Angular 5中复用加载器组件:如何实现各子组件独立控制加载状态?
优雅解决Angular 5子组件独立加载器问题
嘿,这个需求太常见了!重复写加载器触发逻辑确实既冗余又难维护,给你分享两个在Angular 5里非常实用的优雅方案,帮你彻底摆脱重复代码:
方案一:全局加载器服务+自定义指令
核心思路是用一个全局服务维护所有子组件的加载状态,再通过自定义指令把加载器和状态绑定起来,子组件只需要调用服务的方法切换状态即可。
第一步:创建加载器状态服务
这个服务负责管理每个组件的加载状态,用Map存储不同组件的状态流,确保每个子组件的状态独立:
// loader.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class LoaderService { private loaderStates = new Map<string, BehaviorSubject<boolean>>(); // 获取指定组件的加载状态流 getLoaderState(key: string): Observable<boolean> { if (!this.loaderStates.has(key)) { this.loaderStates.set(key, new BehaviorSubject<boolean>(false)); } return this.loaderStates.get(key)!.asObservable(); } // 设置指定组件的加载状态 setLoaderState(key: string, isLoading: boolean): void { const subject = this.loaderStates.get(key); if (subject) { subject.next(isLoading); } } // 组件销毁时清理状态,避免内存泄漏 clearLoaderState(key: string): void { const subject = this.loaderStates.get(key); if (subject) { subject.complete(); this.loaderStates.delete(key); } } }
第二步:自定义加载器指令
用指令绑定加载器模板和状态,自动根据状态显示/隐藏加载器:
// loader.directive.ts import { Directive, Input, OnInit, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core'; import { LoaderService } from './loader.service'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @Directive({ selector: '[appLoader]' }) export class LoaderDirective implements OnInit, OnDestroy { @Input('appLoader') loaderKey!: string; private destroy$ = new Subject<void>(); constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private loaderService: LoaderService ) {} ngOnInit(): void { this.loaderService.getLoaderState(this.loaderKey) .pipe(takeUntil(this.destroy$)) .subscribe(isLoading => { if (isLoading) { // 显示加载器模板 this.viewContainer.clear(); this.viewContainer.createEmbeddedView(this.templateRef); } else { // 隐藏加载器 this.viewContainer.clear(); } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); this.loaderService.clearLoaderState(this.loaderKey); } }
第三步:子组件中使用
子组件只需要在模板定义加载器,然后在API请求前后切换状态:
// child-a.component.ts import { Component, OnInit } from '@angular/core'; import { LoaderService } from '../loader.service'; import { DataService } from '../data.service'; @Component({ selector: 'app-child-a', template: ` <!-- 定义加载器模板,通过指令绑定唯一key --> <ng-template appLoader="childA"> <div class="loader-spinner">子组件A加载中...</div> </ng-template> <div *ngIf="data" class="content">子组件A数据:{{ data }}</div> ` }) export class ChildAComponent implements OnInit { data: any; constructor( private loaderService: LoaderService, private dataService: DataService ) {} ngOnInit(): void { // 开始请求,设置加载状态为true this.loaderService.setLoaderState('childA', true); this.dataService.fetchChildAData().subscribe({ next: (res) => { this.data = res; // 请求完成,关闭加载器 this.loaderService.setLoaderState('childA', false); }, error: () => { // 出错也要关闭加载器 this.loaderService.setLoaderState('childA', false); } }); } }
方案二:封装带加载状态的API服务
如果你的加载器只和API请求绑定,可以把加载状态逻辑直接封装到数据服务里,子组件无需手动切换状态,更简洁:
封装数据服务
// data-with-loader.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, BehaviorSubject } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class DataWithLoaderService { private loaderStates = new Map<string, BehaviorSubject<boolean>>(); constructor(private http: HttpClient) {} getLoaderState(key: string): Observable<boolean> { if (!this.loaderStates.has(key)) { this.loaderStates.set(key, new BehaviorSubject<boolean>(false)); } return this.loaderStates.get(key)!.asObservable(); } // 封装GET请求,自动处理加载状态 getWithLoader<T>(url: string, loaderKey: string): Observable<T> { this.loaderStates.get(loaderKey)?.next(true); return this.http.get<T>(url).pipe( tap({ complete: () => this.loaderStates.get(loaderKey)?.next(false), error: () => this.loaderStates.get(loaderKey)?.next(false) }) ); } clearLoaderState(key: string): void { const subject = this.loaderStates.get(key); if (subject) { subject.complete(); this.loaderStates.delete(key); } } }
子组件使用示例
// child-b.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { DataWithLoaderService } from '../data-with-loader.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-child-b', template: ` <ng-template appLoader="childB"> <div class="loader-spinner">子组件B加载中...</div> </ng-template> <div *ngIf="data" class="content">子组件B数据:{{ data }}</div> ` }) export class ChildBComponent implements OnInit, OnDestroy { data: any; private destroy$ = new Subject<void>(); constructor(private dataWithLoaderService: DataWithLoaderService) {} ngOnInit(): void { // 直接调用封装后的方法,无需手动切换加载状态 this.dataWithLoaderService.getWithLoader('/api/child-b-data', 'childB') .pipe(takeUntil(this.destroy$)) .subscribe(res => { this.data = res; }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); this.dataWithLoaderService.clearLoaderState('childB'); } }
关键注意事项
- 唯一Key:每个子组件的
loaderKey必须唯一,建议用组件类名(如ChildAComponent.name)或者组件实例的唯一标识,避免状态冲突; - 内存泄漏:务必在组件销毁时调用
clearLoaderState方法,清理对应的状态流; - 自定义加载组件:如果你的加载器是独立组件(比如
<app-loader>),可以在指令中用ComponentFactoryResolver动态创建,替代ng-template; - Async管道简化:可以直接在模板中用
async管道订阅加载状态,比如:<app-loader *ngIf="(loaderService.getLoaderState('childA') | async)"></app-loader>
内容的提问来源于stack exchange,提问作者Matthew Trow
相关产品推荐
相关产品推荐

