greenie2600
greenie2600

Reputation: 1699

Angular 4 services: how can I implement an async init process?

I'm building an Angular 4 service which provides access to certain data. When this service is first instantiated, it needs to establish a connection to the data store, which is an asynchronous process.

How can I best prevent consumers of this service (i.e., components or other services) from attempting to use the service before this async connection process has completed?

Is there some way my service can tell Angular's bootstrapper to wait for this promise to resolve before proceeding?

If it matters, the service is registered in the providers array of my @NgModule. So (as I understand it), Angular will construct a single instance of the service, which will be provided to any components or other services which inject MyService.

To be explicit, here's the scenario I'm afraid of:

  1. During the application bootstrap process, Angular sees MyService in the module's providers array, and invokes its constructor.

  2. MyService's constructor begins the connection process, which could take a second or two.

  3. Meanwhile, Angular's bootstrapper keeps charging ahead, and renders my application's dashboard.

  4. The dashboard component injects MyService, and (in its own constructor, or perhaps in ngOnInit) attempts to call myService.getData() before the connection has been established.

To illustrate, here's what this service looks like:

import { Injectable } from '@angular/core';

@Injectable()
export class MyService {

    private connection;

    constructor() {

        this.connect().then((connection) => {
            this.connection = connection;
            // Components should be able to consume this service once execution reaches this point, but not before!
        });

    }

    private connect(): Promise<any> {
        // (return some promise which will eventually resolve with a connection)
    }

    public getData(key: string): string {
        // (this method won't be usable until the .then() in the constructor has run)
    }


}

Upvotes: 6

Views: 5911

Answers (1)

Madhu Ranjan
Madhu Ranjan

Reputation: 17894

what you need to do is delay app initialization until your connection gets established. you may do it by using APP_INITIALIZER token like below,

complete Plunker!!

onAppInit Factory returning a initialize promise

export function onAppInit(svc: MyService): () => Promise<any> {
  return svc.initialize;
}

AppModule

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  providers: [
    MyService,
    {
      provide: APP_INITIALIZER,
      useFactory: onAppInit,
      deps: [MyService],
      multi: true
    }
  ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

MyService

@Injectable()
export class MyService{
  connection;
  
  constructor(){}
  
  initialize = (): Promise<any> => {
     return new Promise( (resolve, reject) =>  { 
         setTimeout(() => { 
          this.connection = {
             data : {
               "var1": "xyz" 
             }
          };
          resolve();
         }, 3000);
     });
  }
  
  getData = () => {
    console.log(this.connection);
    return this.connection.data;
  }
}

AppComponent

@Component({
  selector: 'my-app',
  template: `<h1>Hello</h1>
  <hr />
  {{data | json}}
  `
})
export class AppComponent { 
  data;
  constructor(private svc: MyService){ }
  
  ngOnInit(){
    this.data = this.svc.getData();
  }
}

This works as you asked in your problem, but note that your application will not render until the service is initialized, and that may be bad user experience depending on how much delay is acceptable

The better solution would be return a promise or an Observable from getData() as @JBNizet suggested in comment.

Upvotes: 9

Related Questions