Async initialization of Angular 2 service

Vegard Larsen picture Vegard Larsen · Apr 3, 2016 · Viewed 7.5k times · Source

I have an Angular 2 service that needs to do async work when it is initalized, and should not be usable before this initialization has been completed.

@Injectable()
export class Api {
    private user;
    private storage;

    constructor(private http: Http) {
        this.storage = LocalStorage;
        this.storage.get('user').then(json => {
            if (json !== "") {
                this.user = JSON.parse(json);
            }
        });        
    }

    // one of many methods
    public getSomethingFromServer() {
        // make a http request that depends on this.user
    }
}

As it currently stands, this service is initialized, and returned immediately to any component that uses it. That component then calls getSomethingFromServer() in its ngOnInit, but at that point Api.user is not initialized, and therefore the wrong request is sent.

The lifecycle hooks (OnInit, OnActivate, etc) does not work with services, only components and directives, so I cannot use those.

Storing the Promise from get() call would require all the different methods that depend on user to wait for it, causing a lot of code duplication.

What is the recommended way to do async initialization of services in Angular 2?

Answer

Vegard Larsen picture Vegard Larsen · Apr 6, 2016

After working with Thierry's answer for a bit, I discovered that it would only work once, but it did set me on the right path. I had to instead store the promise of the user, and create a new observable which is then flatMap-ed.

@Injectable()
export class Api {
  private userPromise: Promise<User>;

  constructor(private http: Http) {
    this.userPromise = LocalStorage.get('user').then(json => {
      if (json !== "") {
        return JSON.parse(json);
      }
      return null;
    });        
  }

  public getSomethingFromServer() {
      return Observable.fromPromise(this.userPromise).flatMap((user) => {
        return this.http.get(...).map(...);
      });
    }
  }
}

This ensures that the flatMap function gets the user every time it is called, instead of just the first time, as in Thierry's answer.