Reputation: 1307
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
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