下图是 SAP 电商云 Spartacus UI 中的一段和 Site Context 处理逻辑相关的代码:
export const contextServiceProviders: Provider[] = [
BaseSiteService,
LanguageService,
CurrencyService,
{
provide: APP_INITIALIZER,
useFactory: initializeContext,
deps: [ConfigInitializerService, SiteContextRoutesHandler],
multi: true,
},
];
注意第 40 行的标志位,multi
,默认值为 false,这意味着在代码后注册的 initializer 的实现,会覆盖掉先注册的 initializer provider 实现。换言之,这种情况下,APP_INITIALIZER 的 provider 只能有一个。
如果 multi: true
被设置,那么新的提供者会被添加到之前注册的提供者中,使得一个令牌的提供者不止一个。当这个令牌被调用时,angular会执行所有这些令牌。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9CQg0dA-1657529948839)(https://upload-images.jianshu...)]
因此,开发人员可以使用 multi: true
来创建 multi Provider令牌。这意味着我们可以创建多个函数/服务,并在初始化期间调用它。
做一个测试,分别创建两个 initializer 的实现:
export function initializeApp2() {
return (): Promise => {
return new Promise((resolve, reject) => {
console.log(`initializeApp2 called`);
setTimeout(() => {
console.log(`initializeApp2 Finished`);
resolve();
}, 2000);
});
};
}
然后使用 multi:true 给同一个 Injection Token APP_INITIALIZER
注册两个不同的 provider:
providers: [
AppInitService,
{ provide: APP_INITIALIZER,useFactory: initializeApp1, deps: [AppInitService], multi: true},
{ provide: APP_INITIALIZER,useFactory: initializeApp2, multi: true}
],
最后运行时,这两个 initializers 会同时得到触发。
另外,这个例子使用了 useFactory
为 Injection Token 注册 provider.
一个例子:
const WINDOW = new InjectionToken('A reference to the window object', {
factory: () => window,
});
上面的例子,使用工厂函数作为提供者来设置 InjectionToken,就好像它是在应用程序的根注入器中显式定义的一样。 现在我们可以在应用程序的任意位置使用它:
@Component({
selector: 'my-app'
})
export class AppComponent {
constructor(@Inject(WINDOW) window: Window) {}
}
我们可以使用 inject
函数,在工厂函数的执行环境里,来获取其他提供者的引用。 让我们看另一个例子:
import { inject, InjectionToken } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
export type TimespanProvider = Observable;
export const TIMESPAN = new InjectionToken('Subscribe to timespan query param', {
factory() {
const activatedRoute = inject(ActivatedRoute);
return activatedRoute.queryParams.pipe(
pluck('timespan'),
filterNil(),
distinctUntilChanged()
);
},
});
上面的例子,我们注入 ActivatedRoute,使用 inject
获得其 provider 实例,并为时间跨度查询参数返回一个 observable. 将 Observable
导出成新的类型别名:TimespanProvider
.
这个 Injection Token 的注入例子:
@Component({
selector: 'app-home'
})
export class HomeComponent implements OnInit {
constructor(@Inject(TIMESPAN) private timespan$: TimespanProvider) {}
ngOnInit() {
this.timespan$.pipe(untilDestroyed(this)).subscribe(console.log);
}
}
另一个例子:我们有一个 ThemeService 实现类,将用户当前的 Theme 值暴露出去:
@Injectable({ providedIn: 'root' })
export class ThemeService {
private theme = new Subject();
theme$ = this.theme.asObservable();
setTheme(theme: string) {
this.theme.next(theme);
}
}
Component 读取当前 Theme 的值:
@Component({
selector: 'app-hello',
template: `{{ theme$ | async }}
`
})
export class HelloComponent {
theme$: Observable;
constructor(private themeService: ThemeService) {}
ngOnInit() {
this.theme$ = this.themeService.theme$;
}
}
采取 Injection Token 的实现方式:
export type ActiveThemeProvider = Observable;
export const ACTIVE_THEME = new InjectionToken('Active theme', {
factory() {
return inject(ThemeService).theme$;
}
});
首先定义一个新的 Type
,然后使用 factory 方法,返回 Observable
类型的结果。