相互依赖的多个角度APP_INITIALIZER

时间:2019-08-21 05:39:25

标签: angular dependency-injection

背景:我需要在应用程序启动期间执行几次初步检查(1)./assets/config.json文件中读取angular app的配置并获取API从此处结束端点,(2)对第一步中获取的端点进行API调用,并从后端加载一些设置。

目标:能够使用APP_INITIALIZER(例如A和B)初始化两个服务,其中B依赖于A。check out this stackblitz来查看问题

我尝试过的事情:如果图片中没有第二部分(能够向后端发出API请求),那么我设法使用了角度APP_INITIALIZER为了完成工作,我随后搜索了一些文章,发现其中一本Managing dependencies among App Initializers in Angular,其中列出了3种方法,第三种是推荐的方法(因为它易于维护),但是我不太了解所有方法其中,我相信作者没有包括每种方法的完整代码实现(我确实知道是作者要求是否提供代码示例,我可能错了)。如果有经验的人可以分享相同的知识,我将不胜感激。

PS:我没有在这里添加任何代码,因为我不确定我尝试过的内容是否合理,但我很乐意添加一些代码。

Stackblitz1(单个APP_INITIALIZER-https://stackblitz.com/edit/angular-puaw7a

[问题] Stackblitz2(多个APP_INITIALIZER-https://stackblitz.com/edit/angular-7uqijv

3 个答案:

答案 0 :(得分:1)

我认为您实际上并不需要初始化程序。您只是拥有其他服务所依赖的价值。代码中的问题是您有一个异步值,并尝试将其公开为同步值。

我认为,只要将配置公开为Observable并在需要的地方“等待”即可解决您的问题。好处是在完成配置请求之前,应用程序将尽可能多地加载。

例如,shareReplay(1)运算符将把该项保留在内存中,并将HTTP请求推迟到实际需要时:

export class ConfigService {

  configData$ = this.httpClient.get<{config1:string}>('./assets/config.json').pipe(shareReplay(1));

  constructor(private httpClient: HttpClient) { }
}

现在,您的第二项服务可以等待第一项服务中的configData。或者只是通过可观察的管道对其进行转换,然后将其进一步暴露为可观察的形式,以将所有内容推迟到实际需要为止。

@Injectable({
  providedIn: 'root'
})
export class SettingsService {

  settingsData$ = this.configService.configData$.pipe(
    map(c => c.config1 + '...and config service dependent action'),
    shareReplay(1), // also keep the value in memory maybe?
  );

  constructor(
    private httpClient: HttpClient,
    private configService: ConfigService
    ) { }
}
export class HelloComponent implements OnInit {

  @Input() name: string;

  dataFromConfigSvc: Observable<string>;
  constructor(private configService: ConfigService) { }

  ngOnInit() {
    // you could either use async pipe in the template or subscribe here and get the value
    this.dataFromConfigSvc = this.configService.configData$.pipe(map(c => c.config1));
  }

}

答案 1 :(得分:1)

只需使用

useFactory: (appConfigSvc: ConfigService,settingsService:SettingsService) => {
        return () => {
          return appConfigSvc.loadConfig().then(()=>{
            return settingsService.loadConfig()
          });
        };
      }

查看您的forked code in stackblitz

答案 2 :(得分:1)

我知道已经有一段时间了,但我还有另一个解决方案。 我为依赖于配置的初始化函数做了包装。

在应用模块中定义提供者如下:

providers: [
    { provide: APP_INITIALIZER, useFactory: initConfig, deps: [...], multi: true },
    { 
        provide: APP_INITIALIZER,
        useFactory: withConfig(initCrm, [initCrm_Deps]), // use wrapper 
        deps: [Injector], // Injector is required for withConfig
        multi: true
    }
]

包装函数:

export function withConfig(factory: Function, deps: any[]) {
    return (injector: Injector) => {
        return () => AppConfig.instance$
            .toPromise()
            .then(() => {
                // Inject dependencies
                const depsInstances = deps.map(d => injector.get(d));

                // Execute original function
                return factory.apply(globalThis, depsInstances)();
            });
    };
}

AppConfig.instance$ 是一个 Subject,它在加载配置后发出值。