13

I am using APP_INITIALIZER like it is recommended in this answer, with my service returning a promise, but it doesn't always wait for it to resolve and I can see my component console.logging undefined and then the service logging the downloaded object.

I need the app to not do anything before this data is loaded.

app.module.ts

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { Http, HttpModule, JsonpModule } from '@angular/http';
import { UserService } from '../services/user.service';

<...>
@NgModule({
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule,
    JsonpModule,
    routing
  ],
  declarations: [
    AppComponent,
    <...>
  ],
  providers: [
    <...>
    UserService,
    {provide: APP_INITIALIZER,
      useFactory: (userServ: UserService) => () => userServ.getUser(),
      deps: [UserService, Http],
      multi: true
    }
  ],
  bootstrap: [AppComponent]

user.service.ts

@Injectable()
export class UserService {

    public user: User;
    constructor(private http: Http) { }

    getUser(): Promise<User> {
        console.log('get user called');
        var observable= this.http.get('/auth/getuser', { headers: getHeaders() })
            .map(extractData);

        observable.subscribe(user => {this.user = user;
            console.log(this.user)});
        return observable.toPromise();
    }
}
2
  • It looks ok. Please, state which package versions you are using. Are you able to replicate the issue as a fiddle/plunk? Commented Oct 1, 2016 at 1:32
  • I am not sure why you are getting logs with undefined values, but if you use both toPromise() and subscribe() on observable you will call /auth/getuser twice. You should chain also rxjs operator share() (rxjs/add/operator/share) Commented Feb 23, 2017 at 0:57

4 Answers 4

19

Try the following code:

getUser(): Promise<User> {
    console.log('get user called');
    var promise = this.http.get('/auth/getuser', {headers: getHeaders()})
        .map(extractData)
        .toPromise();
    promise.then(user => {
        this.user = user;
        console.log(this.user);
    });
    return promise;
}

I was facing the same problem, and using a promise instead of a observable did the trick for me.

Sign up to request clarification or add additional context in comments.

4 Comments

Hey, I made it work quite some time ago actually, but this is exactly what I did.
I think observables get triggered at the first change of the object, which does not contain all response data, while promises are triggered when the whole task is done. Voted Up as this solved the issue for me as well.
@DesislavKamenov, No, it's "by design" :) Read this thread: github.com/angular/angular/issues/9047
I would give you 200 points if I could! Brilliant!! Thank you!!
3

I think the problem is caused because you subscribe to the observable. This should work

@Injectable()
export class UserService {

    public user: User;
    constructor(private http: Http) { }

    getUser(): Promise<User> {
        console.log('get user called');
        return observable= this.http.get('/auth/getuser', { headers: getHeaders() })
            .map(extractData)
            .do(user => {
                this.user = user;
                console.log(this.user)
             })
            .toPromise();
    }
}

I'm not sure if toPromise() is necessary. I'd expect it to work with Observable as well.

Comments

2

Guard your route with a CanActivate class using a Promise which loads the config settings should also work.

Use the appSettings.service with a function just like the one returning a promise

getAppSettings(): Promise<any> {
        var observable = this.http.get(this.ApiUrl, { headers: this.headers })
            .map((response: Response) => {
                var res = response.json();
                return res;
            });

        observable.subscribe(config => {
        this.config= config;
        console.log(this.config)
        });
        return observable.toPromise();  
    }

And the CanActivate guard as below:

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AppSettingsService } from './appsettings.service';

@Injectable()
export class CanActivateViaAuthGuard implements CanActivate {

//router: Router
    constructor(private appSettingsService: AppSettingsService)
    {
    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
               return this.appSettingsService.getAppSettings().then(() => { 
    return true });
   }
}

This will ensure that your settings are available when the corresponding components are constructed. (using the APP_INITIALIZER did not restrict the constructor being called, so I had to use this technic, Also please make sure, you dont export all the components in the exports:[] in the module)

To guard the routes and ensure settings are loaded before the constructors are called, please use the usual canActivate option in the path for the route definition

 path: 'abc',
 component: AbcComponent,
 canActivate: [CanActivateViaAuthGuard]

The initialization of appsettings should happen before the constructor for AbcComponent is called, this is tested and works in Angular 2.0.1

I'm not sure if it is the right place to load config but seems to serve the purpose

Comments

0

Late, but if you want to keep your Service class returning Observables (I do), call it like this in your App Module class:

function authenticationFactory(service: AuthenticationService) {
  console.log("calling login");
  //Call auth service login to get JWT info and startup data.
  //Also convert from an Observable to a Promise to work with APP_INITIALIZER.
  return () => service.login().toPromise().then(/*do nothing here*/);
}

NgModule Metadata stuff...

providers: [
   ...
    AuthenticationService,
    {
      provide: APP_INITIALIZER,
      useFactory: authenticationFactory,
      deps: [AuthenticationService],
      multi: true
    },
    ...
  ],

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.