How can I create a service in Angular 4 using "APP_INITIALIZER" without involving promises?

I am currently working on an app embedded within an iframe of a parent application.

Upon loading my app within the iframe, I have configured an APP_INITIALIZER in my AppModule called tokenService. This service is responsible for sending a message to the parent application in order to retrieve a token. As a result, there is a "message" event handler included in the token service code.

Here is the snippet of 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" will be triggered upon receiving the response.

Moreover, the "tokenService" has been set as an "APP_INITIALIZER". Here's how it's done:

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

The initialization of the configService is also essential:

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

In the app.module.ts file, there is a specific method:

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

I am currently facing an issue with converting the event handler "processMessage" into a promise method. Can someone provide guidance on this matter? I encounter an error when attempting to run the application which states:

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

Additionally, I would like to ensure that the tokenService completes its execution before any other components within my application are initialized. How can I guarantee that the tokenService finishes execution and the event handler for sendMessage is invoked prior to proceeding with the loading of other components?

Below is the code for the configuration service:

    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;
    }
}

Your assistance is greatly appreciated.

Thank you in advance.

Answer №1

tService.getToken is currently undefined due to a DI error, where tService should actually be replaced with ConfigurationService. The annotation

[ConfigurationService, tokenService]
indicates that 2 dependencies will be injected, but the factory function only accepts 1 parameter.

If ConfigurationService is not being used, it does not need to be injected at all.

The method getToken already returns an observable. Since APP_INITIALIZER expects a promise for asynchronous tasks, the observable must be converted to a promise using:

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

and

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

The issue with ConfigurationService is that it operates asynchronously and only exposes the result of the promise through getConfigurationData eventually. Calling getConfiguration multiple times leads to repetitive requests. It would be better if it exposed 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())
  }
}

With this setup, configurationPromise can be chained anywhere without being limited to promise flow control:

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);
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be causing the presence of a "strike" in my typescript code?

While transitioning my code from JavaScript to TypeScript for the first time, I noticed that some code has been struck out. Can someone explain why this is happening and what it signifies? How should I address this issue? Here's a screenshot as an exa ...

I encountered a mistake: error TS2554 - I was expecting 1 argument, but none was given. Additionally, I received another error stating that an argument for 'params' was not provided

customer-list.component.ts To load customers, the onLoadCustomers() function in this component calls the getCustomers() method from the customer service. customer.servise.ts The getCustomers() method in the customer service makes a POST request to the A ...

Is React Spring failing to trigger animations properly on iOS devices?

I have a code that functions perfectly on my desktop and across all browsers. Each button is designed to trigger a 4-second animation upon load or hover, initiating the playback of various videos. However, there's an issue with iOS where the video or ...

Showing numeric values with decimals in an Angular Handsontable table

I want to display a decimal value (22.45) without rounding while using angular-handsontable in my application. Even though I specified the format, the value is not displayed as expected columns: ({ type: string; numericFormat: { pattern: string; }; } | {} ...

Implementing an All-Routes-Except-One CanActivate guard in Angular2

In order to group routes within a module, I am aware that it can be done like this: canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, ...

Confirming that the NGRX Dictionary value is set

After upgrading from Angular 7.1.4 to 8.2.0 and Typescript from 3.1.6 to 3.5.3, I encountered an issue with identification of array items. Prior to the upgrade, TypeScript correctly recognized that the array item was not undefined. However, post-upgrade, ...

Discover the specifics of an element within angular version 6

My goal is to have the details of a course module displayed when it is clicked. However, I am encountering an error with my current code: Cannot read property 'id' of undefined. coursemoduleapi.service.ts getCourseModule(id:number) { return thi ...

Getting the string value from an observable to set as the source attribute for an

I am facing an issue with my image service. It requires the user_id to retrieve the id of the user's profile picture, followed by another request to get the JPG image associated with that id. To fetch the image, use this code snippet: <img [src]=" ...

Leveraging Angular's catchError method to handle errors and return

One of my challenges involves a model class that represents the server response: class ServerResponse { code: number; response: string; } Whenever I make api calls, I want the response to always be of type Observable<ServerResponse>, even in ...

Is Angular UI's data binding more of a push or pull mechanism? How can I optimize its speed?

Suppose I have a variable a that is displayed in HTML as {{a}}. If I then update its value in TypeScript using a = "new value";, how quickly will the new value be reflected in the user interface? Is there a mechanism that periodically checks all bound var ...

Facing error TS2397: Unable to locate module '*'' when utilizing a personalized library in Angular 6/7

Currently in the process of updating my Angular project from version 2.something to the latest release. Initially, I had a custom angular.config file set up so that I could build two separate apps utilizing the same component 'library'. However, ...

Utilizing constants within if statements in JavaScript/TypeScript

When working with PHP, it is common practice to declare variables inside if statement parenthesis like so: if ($myvar = myfunction()) { // perform actions using $myvar } Is there an equivalent approach in JavaScript or TypeScript?: if (const myvar = myf ...

A guide to activating tag selection within the DevExtreme tag box

I'm currently utilizing devExtereme within my Angular project. My goal is to enable the selection of text within tags in my tagbox component. Here's what I have implemented: <dx-tag-box [dataSource]="sourves" [value]="value&quo ...

Error: The function visitor.visitUnaryOperatorExpr is not defined as a function

I recently started developing an Angular app with a purchased template and collaborating with another developer. Initially, I was able to successfully build the project for production using ng build --prod. However, when trying to build it again yesterday, ...

How to set up scroll restoration for the Angular Standalone Router?

The Angular Router provides the option to restore scrolling functionality, with documentation on how to implement it when loading data. Is there a way to configure the standalone router to automatically scroll back to the top of the router outlet? A demo ...

Revolutionize Your Web Development with ASP.NET Core and Angular 2 Integration using Webpack

I have started a new ASP.NET Core 1.0.1 project and I am working on integrating Angular 2 from scratch with Webpack Module Bundler. My goal is to use Hot Module Replacement (HMR) through ASP.NET Core SpaServices in order to avoid browser reloads, but I am ...

What is the procedure for sending a secondary route parameter to the angular 2 services module?

I'm a beginner with Angular 2 and I'm seeking advice on how to pass multiple parameters to Angular's service module. For instance, here is the first parameter I'm passing to the service through the router link. In the home-component.ht ...

Exploring Iframes within Angular2

import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Greetings, {{name}}!</h1> <iframe src="http://example.com/Home?requestId=+[testRequestId]+" allowfulls ...

Angular's Bootstrap Array: A Powerful Tool for Organizing

Why does an Angular module have a bootstrap array with more than one component listed? Is there a specific example where multiple components in the bootstrap array are necessary? I'm unsure about this concept and would appreciate any insights or examp ...

Tips for accessing an item from a separate TypeScript document (knockout.js)

In the scenario where I need to utilize an object from another TypeScript file, specifically when I have an API response in one.ts that I want to use in two.ts. I attempted exporting and importing components but encountered difficulties. This code snippe ...