user2439903
user2439903

Reputation: 1307

Angular "APP_INITIALIZER" to have service without promise in angular 4?

I have an app which is present in an iframe of parent application.

When my app which is within iFrame loads, in my applications AppModule, i have an APP_INITIALIZER called tokenService. This service sends a window.sendMessage to parent application to get token. So there is a "message" event handler in the token service.

Below is the code:

    import { Injectable } from '@angular/core';
import { ConfigurationService } from './configService';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class tokenService {

  private _configs;
  private msgId = this.newId();
  private messageToGetToken = {
    'id': this.msgId,
    'type': 'test/V/GetToken',
    'data': null
  };

  constructor(private configService: ConfigurationService) {
    this._configs = configService.getConfigurationData();
  }

  getToken() {
    if (this._configs.loginRequired == true) {
      if (window.addEventListener) {
        window.addEventListener('message', this.processMessage, false);
      }
      else {
        (<any>window).attachEvent("onmessage", this.processMessage);
      }

      parent.window.postMessage(JSON.stringify(this.messageToGetToken), '*');

      return Observable.fromEvent(window, 'message')
        .subscribe((messageEvent: MessageEvent) => { this.processMessage(messageEvent); });
    }
  }

  private processMessage(evt) {
    var result = JSON.parse(evt);
    if (result && result.responseFor && result.responseFor === this.msgId) {
      localStorage.setItem('token', result.data ? result.data[0] : null);
      console.log(result.data);
    }
    console.log(evt);
  }

  private newId() {
    return '_' + Math.random().toString(36).substr(2, 9);
  };
}

The method "processMessage" gets called when the result comes back.

The "tokenService" has been set as an "APP_INITIALIZER". Below is the code:

{
      'provide': APP_INITIALIZER,
      'useFactory': loadService,
      'deps': [ConfigurationService, tokenService],
      'multi': true,
    },

The configService is initialized too:

export function loadConfig(config: ConfigurationService): Function {
  return () => config.configuration$;
}
{
      'provide': APP_INITIALIZER,
      'useFactory': loadConfig,
      'deps': [ConfigurationService],
      'multi': true,
}

In app.module.ts file, there is method:

export function loadService(tService: tokenService): Function {
  return () => tService.getToken();
}

I am not sure how to make this event handler: "processMessage" as promise method. Can anyone help me with that? Because i get an error when i try to run the application. The error is:

ERROR TypeError: tService.getToken is not a function
    at Array.eval (app.module.ts:44)
    at ApplicationInitStatus.runInitializers (core.js:3569)

Also, i want to make this tokenService complete its execution, before other components in my application get initialized. How to make sure that tokenService has completed execution and the event handler for sendMessage is called, before the app continues to load other components?

The code for configuration service is below:

    import { Http } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';

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

@Injectable()
export class ConfigurationService {
  private configuration;
    constructor(private http: Http) {
    }

    getConfiguration(): Promise<any> {
      let ret = this.http.get('appConfig.json').map(
        res => this.configuration = res.json())
        .toPromise()
        .then((data: any) => {
          this.configuration = data;
        })
        .catch((err: any) => {
          console.log("error while reading app config!");
        });

      return ret.then((x) => {
      });
    }

    getConfigurationData(): any {
      return this.configuration;
    }
}

any help is appreciated.

Thanks in advance.

Upvotes: 3

Views: 3463

Answers (1)

Estus Flask
Estus Flask

Reputation: 222720

tService.getToken is undefined because DI went wrong, and tService is actually ConfigurationService. [ConfigurationService, tokenService] annotation means that 2 dependencies will be injected, while factory function has only 1 parameter.

If it doesn't use ConfigurationService, it doesn't have to be injected.

getToken already returns an observable. APP_INITIALIZER expects a promise for asynchronous initializations, so an observable should be converted to a promise:

'deps': [tokenService],
  'multi': true,
},

and

export function loadService(tService: tokenService): Function {
  return () => tService.getToken().toPromise();
}

The problem with ConfigurationService is that it is asynchronous yet it exposes only the result of that promise that will be available with getConfigurationData at some point. While calling getConfiguration multiple times will result in recurring requests. It should expose a promise or an observable that can be easily chained:

export class ConfigurationService {
  public configurationPromise = this.getConfiguration().toPromise();
  public configuration;

  constructor(private http: Http) {
    this.configurationPromise.then(configuration => {
      this.configuration = configuration;
    });
  }

  private getConfiguration(): Observable<any> {
    return this.http.get('appConfig.json').map(res => res.json())
  }
}

Then configurationPromise can be chained anywhere and it's not limited to promise control flow:

export class tokenService {
  ...
  constructor(private configService: ConfigurationService) {}

  getToken(): Observable<any> {
    ...
    return Observable.fromPromise(configService.configurationPromise)
    .switchMap(() => Observable.fromEvent(window, 'message'))
    .map((messageEvent: MessageEvent) => this.processMessage(messageEvent))
    .take(1);
  }
}

Upvotes: 1

Related Questions