52

We would like to call a rest api when angular 2 app being bootstrapped, i mean first thing it should do about the application is call this api and get some data which is required for application.

Is there anyway to achieve this? I saw following article but it was meant for beta version of Angular 2

Sample Code based on Angular 2 beta or rc

Reading data before app start up

Vitalii Zurian
  • 17,858
  • 4
  • 64
  • 81
user509755
  • 2,941
  • 10
  • 48
  • 82
  • Just throw it in your main app component, then use its resolution to route to whatever module/component you need that data in – Joshua Ohana Jan 13 '17 at 03:17
  • Thanks Joshua for your reply but I dont want to call any components before this service being called completes, since my components needs this data which is returned by rest api. I was looking for something like APP-INITIALIZER call from App module but problem is I am not able to route user from service I call from APP_INITIALIZER config, since there is cyclic dependency. I added new link in my original comment. – user509755 Jan 13 '17 at 13:46

4 Answers4

112

You can use APP_INITIALIZER to call a service method at bootstrap. You will require to define a provider for it in your AppModule.

Here is an example of how to do this.

StartupService (startup.service.ts)

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class StartupService {

    private _startupData: any;

    constructor(private http: Http) { }

    // This is the method you want to call at bootstrap
    // Important: It should return a Promise
    load(): Promise<any> {

        this._startupData = null;

        return this.http
            .get('REST_API_URL')
            .map((res: Response) => res.json())
            .toPromise()
            .then((data: any) => this._startupData = data)
            .catch((err: any) => Promise.resolve());
    }

    get startupData(): any {
        return this._startupData;
    }
}

AppModule (app.module.ts)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';

import { StartupService } from './startup.service';

// ...
// Other imports that you may require
// ...


export function startupServiceFactory(startupService: StartupService): Function {
    return () => startupService.load();
}

@NgModule({
    declarations: [
        AppComponent,
        // ...
        // Other components & directives
    ],
    imports: [
        BrowserModule,
        // ..
        // Other modules
    ],
    providers: [
        StartupService,
        {
            // Provider for APP_INITIALIZER
            provide: APP_INITIALIZER,
            useFactory: startupServiceFactory,
            deps: [StartupService],
            multi: true
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

EDIT (How to handle startup service failure):

AppComponent (app.component.ts)

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { StartupService } from './startup.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

    constructor(private router: Router, private startup: StartupService ) { }

    ngOnInit() {

        // If there is no startup data received (maybe an error!)
        // navigate to error route
        if (!this.startup.startupData) {
            this.router.navigate(['error'], { replaceUrl: true });
        }
    }

}
efirvida
  • 4,592
  • 3
  • 42
  • 68
Santanu Biswas
  • 4,699
  • 2
  • 22
  • 21
  • One question i had is, what happens if my api call fails, i would like to stop initializing app and show error message, since data retured from this app is important for whole app to work – user509755 Jan 13 '17 at 15:27
  • @user509755 I have added the edit to show how you can read the startup data from `StartupService` in the `AppComponent` and redirect to an error page if the startup data is `null`. – Santanu Biswas Jan 13 '17 at 15:49
  • Glad I could help! – Santanu Biswas Jan 13 '17 at 18:00
  • 1
    Very well explained. Docs are too confusing. Please point this out in the docs so that I can understand it at a deeper level. – Giridhar Karnik May 10 '17 at 15:14
  • 6
    thanks for the provided sample, just wanted to point out that `private startup: ConfigService` probably should be `private startup: StartupService` – belzebu Jun 03 '17 at 00:37
  • How big is the benefit in terms of speed when doing this? Did anyone measure it? – Florian Leitgeb Jul 27 '17 at 11:28
  • 1
    Is there also a way to postpone bootstrap component initialization until promise is resolved? I need to make request for user data which I need on AppComponent initialization. – magos Aug 02 '17 at 13:12
  • @magos Did you succeed in postponing the AppComponent initialization? Any possible solutions? – ShellZero Sep 18 '17 at 18:00
  • @ShellZero - The AppComponent ngOnInit will be called only after the Startup Service is completed. – Santanu Biswas Sep 19 '17 at 08:37
  • `zone.js:654 Unhandled Promise rejection: this.startupService. startupData is not a function ; Zone: ; Task: Promise.then ; Value: TypeError: ` – Stephan Kristyn Oct 08 '17 at 12:51
  • Thank you! Anyway to delivery the config file from the server file system instead, to avoid making an HTTP call before? Would this work even if the app is not Universal? – Sammy Nov 19 '17 at 11:42
  • Hi Shantanu, quick question what if I have to make two separate API Calls before bootstrapping the application, how we can handle in the above example? – Shubham Tiwari Jan 11 '18 at 16:23
  • @ShubhamTiwari you would just need to create a second factory and a second provider. So you would write `export function secondFactory...` that returns the second function you want run and then add another APP_INITIALIZER provider with the useFactory set to secondFactory (or whatever name you give it). – Gallonallen Jan 17 '18 at 02:09
  • @Gallonallen: Thanks for your answer! I could sense it from the above solution itself. I was thinking if this is the best way to do it for the scenario I mentioned. – Shubham Tiwari Jan 17 '18 at 05:17
  • @ShubhamTiwari it is worth noting a few things: 1. If the second method you are trying to call is from a different service than StartupService don't forget to add it as a provider as well. 2. The second method should also return a promise to ensure execution completes before bootstrapping. 3. Most importantly, the two methods will (or should) run concurrently. – Gallonallen Jan 18 '18 at 15:59
  • @ShellZero How did you achieve async function at the startup? I am calling a function that returns Observable<> and AppComponent and my first component loaded before Startup service finished returning data from the server. – Manish Jain Jan 29 '18 at 22:45
  • Anyone stuck with Observable not working...use Promise. https://stackoverflow.com/questions/39725724/angular2-app-initializer-not-consistent – Manish Jain Jan 31 '18 at 19:38
  • @Santanu Biswas but I see a big problem here as magos also mentioned it. the app component oninit will called after app initializer has been finish but it does not mean that the http call has been finished. angular is not waiting for the call to be returned it just call all the lines in start up service and then start to call all the oninit in components. – Meysam Mar 25 '18 at 09:08
  • @Meysam - Angular executes the provided function when the app is initialized and if the function returns a Promise then the initialization is delayed until the Promise is resolved. Hope that answers your query. – Santanu Biswas Mar 25 '18 at 09:35
  • How to read the response from load method in app module itself? – Shweta Sep 16 '21 at 11:10
13

One thing I came across using the answer provided by Santanu is that it won't work unless StartupService is defined in the providers before the APP_INITIALIZER. I'm using Angular 4.x, so the providers part looks like this in my case:

providers: [
  UserService,
  {
    provide: APP_INITIALIZER,
    useFactory: userServiceFactory,
    deps: [UserService],
    multi: true
  }
]
Tim
  • 5,435
  • 7
  • 42
  • 62
Dusan Mancic
  • 141
  • 1
  • 6
  • 1
    Do you know if anything related to this has been affected by the providedIn: 'root' feature of newer versions of angular? – Jessy May 01 '20 at 23:14
0

@efirvida & @Santanu Biswas

I tried your answer and it works (content in APP_INITIALIZER happens before app.component bootstrapping).

Unfortunately, the injections in all components happen before it, so that components refer to variables not ready yet.

I opened a question here.

It is basically the same question that @magos asked.

Any help well accepted ^^

The only solution I can think of is to make API calls in app.component to get config data, but is not properly what is needed for both OP and me. Will be my solution if I can't solve the syncronism issue

Eaden
  • 33
  • 8
-2

Here is an article which explains APP_INITIALIZER in much more detail and it also shows how angular uses it.

NR Ganesh
  • 111
  • 5
  • 3
    [When someone goes on Stack Exchange, the question "answer" should actually contain an answer. Not just a bunch of directions towards the answer.](https://meta.stackexchange.com/a/8259/171858) – Erik Philips Sep 27 '19 at 00:10
  • I don't even know how to respond to that question, it doesn't make sense. Especially considering I linked my entire comment to the explaination. – Erik Philips Jun 10 '20 at 23:20