Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular multiple APP_INITIALIZER that depend on each other

Background: I need to perform couple of initial checks during the application start up (1) read angular app config from ./assets/config.json file and get the API end point from there, (2) make an API call to end point retrieved in first step and load some settings from back end.

Goal: be able to initialize two services using APP_INITIALIZER (say A & B), where B has a dependency on A. check out this stackblitz to see the problem

Things I've tried: If second part (being able to make an API request to the back-end) was not in the picture, then I managed to use angular APP_INITIALIZER to get things done, I then searched for some articles and found this one Managing dependencies among App Initializers in Angular, which has 3 approaches listed, 3rd one being the recommended one (as it is easy to maintain), but I don't really understand all of it, I believe author has not included full code implementation of each approach (I do get that it's authors call whether to provide code samples or not, and I might be wrong). I would really appreciate if anyone with experience could share their knowledge wrt the same.

PS: I haven't added any code in here as I am not really confident if what I have tried is sensible or not, but happy to add some code.

Stackblitz1 (single APP_INITIALIZER) - https://stackblitz.com/edit/angular-puaw7a

[The Problem] Stackblitz2 (multiple APP_INITIALIZER) - https://stackblitz.com/edit/angular-7uqijv

like image 351
AD8 Avatar asked Aug 21 '19 05:08

AD8


2 Answers

just use

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

See your forked code in stackblitz

like image 106
Eliseo Avatar answered Oct 14 '22 23:10

Eliseo


I don't think you actually need an initializer in your case. You just have a value that other services depend on. The problem in your code is that you have an async value and try to expose it as a sync value.

I think your problems would be solved if you would just expose the config as an Observable and "await" it where it's needed. The benefits are that the application loads as much as it can until the config requests are done.

For example the shareReplay(1) operator will keep in memory the item and will defer the HTTP request until it's actually needed:

export class ConfigService {

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

  constructor(private httpClient: HttpClient) { }
}

Now your 2nd service can await the configData from the 1st service. Or just transform it via observable piping and expose it further as an observable to defer everything until it's actually needed.

@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));
  }

}
like image 41
Andrei Tătar Avatar answered Oct 14 '22 21:10

Andrei Tătar