Make sure the static variable is set up prior to injecting the provider

In our Angular6 application, we utilize a globalcontextServiceFactory to initialize the application before rendering views.

This process involves subscribing to get configuration from a back-end endpoint and then using forkJoin to retrieve environment application data after launch.

An issue arises with a provider that uses a static variable to store the configuration obtained from the subscription. This provider is set up with provideIn: 'root' injector considering hierarchical dependency injectors.

app.module.ts


export function contextServiceFactory(contextService: ContextService): Function {return () => contextService.init();}


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

context.service.ts

@Injectable()
export class ContextService{
  constructor(...){}

init() {
       this.configSubscription = this.getConfig().subscribe((config: Config) => {
      ConfigService.config = config;
        this.globalSubscription = forkJoin(
        this.getDatas1(),
        this.getDatas2(),
        this.getDatas3()
      ).subscribe((response: Object) => {
        this.setDatas1(response[0]),
        this.setDatas2(response[1]),
        this.setDatas3(response[2])
        this.contextInitialized.next(true);
        this.inj.get(Router).initialNavigation(); // <-- Init router when all responses are retrieved
      });
    });

config.service.ts

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  private static _configuration: Config;

  public static get config(): Config {
    return ConfigService._configuration;
  }

  public static set config(config: Config) {
    ConfigService._configuration = config;
  }
}

test.service.ts

@Injectable({
  providedIn: 'root'
})
export class TestService {

  foo: boolean;

  constructor(private contextService: ContextService) {
// We encounter an error here because TestService is called after ContextService but before the static variable is initialized by init()
    this.foo = ConfigService.config.bar;
  }
}

We see this error in the console: "ERROR TypeError: Cannot read property 'bar' of undefined at new TestService (test.service.ts)"

Question: Is it feasible to wait for the application to be fully loaded before utilizing a static value stored in another service?

Thanks

Answer №1

If we consider going asynchronous using rx as the most straightforward approach:

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  private static _configuration = new ReplaySubject<Config>(1);

  public static config$ = this._configuration.asObservable();

  public static setConfig(config: Config) {
    this._configuration.next(config);
  }
}

However, the entire process needs to be async (which isn't necessarily a drawback in my view):

@Injectable({
  providedIn: 'root'
})
export class TestService {

  foo$: Observable<boolean> = ConfigService.config$.pipe(map(config => config.foo));

  // You can even eliminate the contextService dependency :), config is updated upon availability
  constructor(private contextService: ContextService) {

  }

  async getFooValue(): Promise<boolean> {
    const firstFooValue = await this.foo$.pipe(first()).toPromise();
    // This fetches the initial value or awaits it (wrapped in a promise)
    return firstFooValue;
  }
}

In response to your query, yes, it is feasible to stall the application until content loads, but this may not offer the best user experience and still requires the use of promises/observables for "blocking":

Your contextservice.init should be asynchronous (returning a promise or an observable) for Angular to determine completion:

This modification might address the issue:

async init() {
    const config = await this.getConfig().toPromise();
    ConfigService.config = config;
    const response = await forkJoin(
        this.getDatas1(),
        this.getDatas2(),
        this.getDatas3()
    ).toPromise();
    this.setDatas1(response[0]);
    this.setDatas2(response[1]);
    this.setDatas3(response[2]);
    this.contextInitialized.next(true);
    this.inj.get(Router).initialNavigation(); // <-- Init router when all responses are retrieved
}

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

Ways to include x-api-key in Angular API request headers

I am attempting to include the x-api-key header in the headers, as shown below: service.ts import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptions, Response } from '@angular/http'; import { Observable } from ...

Allow only specified tags in the react-html-parser white list

Recently, I've been working on adding a comments feature to my projects and have come across an interesting challenge with mentioning users. When creating a link to the user's profile and parsing it using React HTML parser, I realized that there ...

The object literal's property 'children' is assumed to have a type of 'any[]' by default

Is there a way to assign the property myOtherKey with any value? I encountered a Typescript error that says.. A problem occurred while initializing an object. The property 'children' in the object literal implicitly has an array type of 'a ...

What is the process of 'initializing' an object in TypeScript?

Is it possible that retrieving a json from a mongodb database and casting it does not trigger the typescript constructor? What could be causing this issue? I have a Team class export class Team { transformations: { [transformationId: string]: Transfor ...

Cancel promise chain after action dispatch (rxjs)

My goal is to achieve the following: this.jobManager .queue( // initiate a job ) .then( // perform additional actions, but stop if `ABORT` action is dispatched before completion ) .finally( // carry out necessary ...

Encountering an error: "Unable to assign the 'id' property to an undefined object while attempting to retrieve it"

I'm running into an issue while attempting to retrieve a specific user from Firebase's Firestore. export class TaskService { tasksCollection: AngularFirestoreCollection<Task>; taskDoc: AngularFirestoreDocument<Task>; tasks: Obs ...

Tips for implementing a method to switch CSS properties of a main container by using a checkbox within its child element in a Svelte component

It took me a while to figure this out, but I still feel like my implementation is not ideal. I'm confused as to why things break when I remove the checkedActivities.has(activity) ? "checked" : "unchecked", because I thought TypeScr ...

Utilizing BehaviorSubject to dynamically display components based on conditions

I have a basic Service that looks like this: import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable() export class HighlightsService { private _highlightedTab: string = ''; highli ...

Experimenting with a TypeScript function containing a subscription operation

Currently, I am experimenting with Jasmine/Karma while working on an Angular 4 project. The issue I'm facing involves testing a function that seems to have trouble setting the 'name' property: https://i.stack.imgur.com/3q49i.jpg The assign ...

Guide on incorporating a bespoke cordova plugin into your Ionic 4 project

After successfully completing all the necessary steps to create a new Cordova plugin as outlined in the link below: Start android activity from cordova plugin I executed the command cordova plugin ls which returned the following result: com.example.sam ...

An error has occurred with mocha and ts-node unable to locate the local .d.ts file

This is the structure of my project: |_typetests | |_type.test.ts | | myproj.d.ts tsconfig.json Here is how my tsconfig.json file is configured: { "compilerOptions": { "module": "commonjs", "moduleResolution": "node", "lib": ...

AngularFire 2 dispatching email for password reset

I am looking to add a feature for resetting passwords or handling forgotten passwords using AngularFire2. It looks like the function sendPasswordResetEmail is either not available in AngularFire2 or the typings have not been updated yet. I tried accessing ...

Obtaining the initial value from an Observable in Angular 8+

Initially, I have a page form with preset values and buttons for navigating to the next or previous items. Upon initialization in ngOnInit, an observable provides me with a list of 3 items as the starting value - sometimes it may even contain 4 items. Ho ...

Guide on altering an element's attribute in Angular 2

Is there a way in Angular to dynamically change the attribute of an HTML element? I want to create a button that can toggle the type attribute of an input from password to text. Initially, I thought about implementing it like this: Template: <input na ...

SSR routing with parameters in Angular Universal seems to be failing after intial navigation

I'm currently experiencing an issue with routing using path parameters: Navigation works perfectly when moving between categories initially, but once I navigate from one category to another, the routing doesn't update even though the URL in the ...

The Ionic and Angular application solely displays dynamic HTML with no encapsulation using ViewEncapsulation.None

I'm struggling to grasp the concept of encapsulation: ViewEncapsulation.None within the @Component. @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], encapsulation: ...

InvalidAction: The function forEach cannot be applied to "res"

Here is the HTML code that I am currently working with: <div *ngIf="chart" class="col-xl-4 col-lg-6"> <div class="card cardColor mb-3"> <div class="card-header headColor"> <img class="img-fluid" src="../../../ ...

What steps should be followed in order to generate a child or item with no assigned key

Here is my TypeScript code designed to automatically record the time of data creation: import * as functions from 'firebase-functions'; export const onAccCreate = functions.database .ref('/Agent/{AgentID}') .onCreate((snapshot, contex ...

Is it not possible to utilize inline "if" statements in conjunction with useEffect within a functional React component?

I'm currently working on integrating Okta's React SDK into a TypeScript-based application using functional components instead of class-based ones. My main challenge lies in rendering a part of the app's menu based on the user's authenti ...

What is the best way to provide JSON data in Angular?

I am working on an Angular 4 application that is using Webpack, and I am currently facing a challenge with serving a JSON file. I have two main questions regarding this: When the JSON file is static, I am struggling to configure Webpack to handle it the ...