【问题标题】:How to implement page loader system inside http service in Angular如何在 Angular 中的 http 服务中实现页面加载器系统
【发布时间】:2020-03-12 12:10:51
【问题描述】:

我正在开发一个 Angular 应用程序作为业余项目,并尝试使用最佳实践编写干净的代码。

我创建了一个“.loader”css 类并将其放在 app-component 的主 div 上。我的目的是在 BackendService.loading 字段为 true 时附加此类,并在 false 时分离。这将为整个页面创建加载效果。我计划在调用端点时将此 BackendService.loading 更改为 true,并在提供响应时更改为 false。我创建了这个 BackendService 来处理所有其他服务的 http 调用。这样我就不必在每个订阅的响应回调的每个组件中都设置这个变量了。

问题:当一个新的 api 调用请求到来时,我可以从 BackendService 设置变量,但是当它结束时我不能再次将它设置为 false,因为它的订阅是在组件控制器中完成的。

如何在提供响应时将 BackendService 中的加载变量设置为 false? (我读了太多关于 Observable 的东西,并试图将 http.get 的返回值包装在另一个 observable 中,但它们都不起作用)或者我需要实现其他更干净的东西吗?

后端服务:

export class BackendService {
  apiUrl = 'http://localhost:8080';

  loading = false;

  token = null;

  constructor(private http: HttpClient) {}

  getOptions() {
    return {
      headers: new HttpHeaders()
        .set('Authorization', this.token)
        .set('Content-type', 'application/json')
        .set('Access-Control-Allow-Origin', '*')
    };
  }

  getRequest(path: string): Observable<any> {
    return this.http.get(this.apiUrl + path, this.getOptions());
  }

  postRequest(path: string, body: Object): Observable<any>  {
    return this.http.post(this.apiUrl + path, body, this.getOptions());
  }
}

随机使用BackendService进行api调用的其他服务:

export class StudentService {

  constructor(private backendService: BackendService) {}

  updateLevel(studentId: number) {
    return this.backendService.getRequest(
      '/student/update-level/' + studentId + '/6/turkce/paragraf');
  }

  getSummary(studentId: number) {
    return this.backendService.getRequest(
      '/student/summary/' + studentId) as Observable<StudentSummaryModel>;
  }
}

组件控制器内部的 api 调用:(这里我必须在结果回调中注入 BackendService.loading)

export class QuizStartComponent implements OnInit {

  constructor(private router: Router,
              private quizService: QuizService,
              private studentService: StudentService,
              private userService: UserService) { }

  ngOnInit() {
  }

  quizStart(quizSize: number) {
    this.quizService.quizSize = quizSize;

    this.studentService.updateLevel(this.userService.user.id).subscribe(
      res => {
        this.router.navigate(['/student/question']);
      }, err => {

      });

  }
}

不相关,但这些是 app.component.ts 和 loader css 类:

<app-navigation></app-navigation>
<div class="container mb-auto" [ngClass]="{'loading': backendService.loading}">
  <div class="mt-3">
    <router-outlet></router-outlet>
  </div>
</div>
<app-footer></app-footer>

.loading {
  ... some css
}

.loading:after {
  ... some css
}

【问题讨论】:

  • 它通常是一个 bool 标志和微调组件上的 *ngIf 的问题。
  • 实际上,这里的问题是如何以及在何处设置单个布尔标志。我们是否需要为每次调用设置它,或者我们是否可以使其更通用以保持代码清洁。 @Antoniossss
  • 在每个明确意图的代码中添加 1 行代码对我来说似乎很清楚。

标签: angular typescript redux-observable


【解决方案1】:

我通常发现定义一个服务很有用,我可以在任何需要显示微调器或为应用设置某些加载状态的地方注入该服务。

告诉应用设置加载状态或微调器的简单服务:

@Injectable()
export class LoadingService {
    loading$ = new Subject<Boolean>();

    appLoading(isLoading: boolean){ this.loading$.next(isLoading); )
}

然后在其他一些管理一些业务逻辑的服务中:

@Injectable()
export class BusinessLogicService {
    constructor(private http: HttpClient, private loadingService: LoadingService){}

    startQuiz(quizId: number): Observable<any> {
       this.loadingService.appLoading(true);
       return this.http.put('quizes' + quizId + '/start', {})
           .pipe(tap( () => {
                //request succeeded, it's no longer loading.
                this.loadingService.appLoading(false);
            }));
           .pipe(catchError(err => {      
                //request failed, it's not loading and let any downstream error handling occur
                this.spinner.hide();
                throw err;
            }));
    }    
}

那么你的应用组件就可以订阅加载状态了:

<app-navigation></app-navigation>
<div class="container mb-auto" [ngClass]="{'loading': isLoading}">
  <div class="mt-3">
    <router-outlet></router-outlet>
  </div>
</div>
<app-footer></app-footer>


export class AppComponent implements OnInit, OnDestroy {
   isLoading = false;
   loadingSub: Subscription;

   constructor(private loadingService: LoadingService){}

   ngOnInit(){
       this.loadingSub = this.loadingService.loading$.subscribe(isLoading => {
           this.isLoading= isLoading;
       });
   }

   ngOnDestroy(){
      //unsub to avoid leaks
      this.loadingSub.unsubscribe();
   }

}

【讨论】:

    【解决方案2】:

    关于如何做一个加载器有很多意见,所以我不会深入探讨。至于在每个请求完成后做些什么,你应该做的就是把它 pipe() 出来并使用 tap():

      getRequest(path: string): Observable<any> {
        this.loading = true;
        return this.http.get(this.apiUrl + path, this.getOptions()).pipe(tap(() => this.loading = false));
      }
    

    https://www.learnrxjs.io/operators/utility/do.html

    【讨论】:

    • 谢谢你成功了!它导致 ExpressionChangedAfterItHasBeenCheckedError 但它可以很容易地解决,如这篇文章中所述:stackoverflow.com/a/46815744/12383396
    • 据我所知,如果出现错误,这将不起作用。加载不会被篡改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    相关资源
    最近更新 更多