The initialization process is failing to trigger before the factory function is executed

I'm utilizing APP_INITIALIZER to load environment-specific variables. The challenge I am facing is needing to access these variables inside my authConfigFactory, but the factory initiates before the completion of APP_INITIALIZER within the app configuration.

The library being used is: https://github.com/manfredsteyer/angular-oauth2-oidc

My goal is to utilize the value of

APP_CONFIG.authConfig.allowedUrls
in my auth config factory. How can I ensure that the configuration sets first before the auth factory?

An error encountered in the factory is:

Cannot read property 'authConfig' of undefined

app.module.ts:

providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: (config: AppConfigService) => () => config.load(),
      multi: true,
      deps: [AppConfigService]
    },
    {
      provide: OAuthModuleConfig,
      useFactory: authConfigFactory
    }
]

app.config.ts:

export let APP_CONFIG: AppConfig;

@Injectable()
export class AppConfigService {
  constructor(
    private injector: Injector
  ) {}

  config = null;

  public load() {
    const http = this.injector.get(HttpClient);

      return http
        .get('../assets/config/config.json')
        .pipe(
          tap(returnedConfig => {
            const t = new AppConfig();
            APP_CONFIG = Object.assign(t, returnedConfig);
          })
        )
        .toPromise();
    }

}

auth-config-factor:

export function authConfigFactory(): OAuthModuleConfig {
  return {
    resourceServer: {
      allowedUrls: APP_CONFIG.authConfig.allowedUrls,
      sendAccessToken: true
    }
  };
}

Answer №1

I encountered a similar issue in the past and after exploring various options unsuccessfully, I found that utilizing ngrx/store was the only viable solution.

Within app.config.ts, you can trigger an action to store the configuration data and access it in other services by using: store.select(), subscribing to it, and managing your workflow accordingly.

app.module.ts

providers: [
  AuthService, // referring to oidc-client.ts where the JSON config is needed
  DataService,
  ConfigService,
  {
    provide: APP_INITIALIZER,
    useFactory: loadConfig,
    deps: [ConfigService],
    multi: true,
  },

config.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { IAppStore } from '../models/store.model';
import * as ConfigActions from '../store/actions/config.actions';

@Injectable()
export class ConfigService {
  public config: any = {};

  constructor(private http: HttpClient, private store: Store<IAppStore>) {}

  getConfig(key: string): string {
    return this.config[key] || '';
  }

  public loadConfig() {
    return new Promise((resolve, reject) => {
      this.http.get('app-config.json').subscribe(
        (response) => {
          this.config = response;
          this.store.dispatch(new ConfigActions.AddConfig(response)); // dispatching action to update the store
          resolve(true);
        }
      );
    });
  }
}

AuthService

import { Log, User, UserManager, WebStorageStateStore } from 'oidc-client';
...
@Injectable()
export class AuthService {
  private _userManager: UserManager;
  public _user: User;

  constructor(
    private store: Store<IAppStore>,
    private httpClient: HttpClient,
    private route: Router,
    private configs: ConfigService
  ) {
    this.store.select('appConfig').subscribe((configdata) => {
      Log.logger = console;
      const config = {
        authority: configdata.stsAuthority,
        client_id: configdata.clientId,
        redirect_uri: `${configdata.clientRoot}/#/auth-callback#`,
        scope: 'openid profile fusionApi.full_access',
        response_type: 'id_token token',
        post_logout_redirect_uri: `${configdata.clientRoot}?postLogout=true`, // clear stored tokens after logout
        userStore: new WebStorageStateStore({ store: window.localStorage }),
        automaticSilentRenew: true,
        silent_redirect_uri: `${configdata.clientRoot}/assets/html/silent-refresh-redirect.html`,
      };
      this._userManager = new UserManager(config);
      this._userManager.getUser().then((user) => {
        if (user && !user.expired) {
          this._user = user;
        }
      });
    }
  }

  login(): Promise<any> {
    return this._userManager.signinRedirect();
  }
  ...

Answer №2

I encountered a similar issue and successfully resolved it.

module-factory:

import { InjectionToken } from '@angular/core';
import { HttpClient, HttpBackend } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { AuthorizationService } from './authorization.service';

export const AUTH_CONFIG = new InjectionToken<string>('auth.config.path', {
    factory: () => 'config.json',
  });


  export function VerifyAuthorizationState(handler: HttpBackend, authService: AuthorizationService, path: string) {
    return async () => {
      const http = new HttpClient(handler);
      // retrieve configuration for authorization
      await http.get(path)
        .pipe(
          map((response: any) => {
            authService.init(response);
          })
        ).toPromise();
      // check authorization status
      await authService.checkAuthorization();
    };
  }

module:

import { NgModule, APP_INITIALIZER, ModuleWithProviders, InjectionToken } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthorizationService } from './authorization.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthorizationGuardService } from './authorization-guard.service';
import { AuthorizationInterceptor } from './authorization-interpretator';
import { HttpBackend, HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { VerifyAuthorizationState, AUTH_CONFIG } from './module-factories';

@NgModule({
  imports: [
    CommonModule
  ],
  providers: [
    AuthorizationService,
    AuthorizationGuardService,
    {
      provide: APP_INITIALIZER,
      useFactory: VerifyAuthorizationState,
      deps: [HttpBackend, AuthorizationService, AUTH_CONFIG],
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthorizationInterceptor,
      multi: true
    }
  ],
  declarations: []
})

export class AuthorizationModule {
  static forRoot(path: string): ModuleWithProviders {
    return {
      ngModule: AuthorizationModule,
      providers: [
        { provide: AUTH_CONFIG, useValue: path }
      ]
    };
  }
}

Sharing my authorization library with you: https://www.npmjs.com/package/sso-authorization-oidc-client

Answer №3

Today, I encountered an issue and found a solution that may be beneficial to others.

This resolution is particularly related to the use of angular-oauth2-oidc.

export const oauthModuleConfigFactory = (config: AppConfig): OAuthModuleConfig => ({
    resourceServer: {
        allowedUrls: [config.apiUrl],
        sendAccessToken: true
    }
});

// An adjustment for App Initializers issue
const fixForAppInitializers = ({ ngModule, providers = [] }: ModuleWithProviders<any>) => ({
    ngModule,
    providers: [...providers.filter((value: any) => value.provide !== OAuthModuleConfig)]
});

@NgModule({
    imports: [fixForAppInitializers(OAuthModule.forRoot())],
    providers: [
        AuthGuard,
        { provide: OAuthModuleConfig, useFactory: oauthModuleConfigFactory, deps: [APP_CONFIG] },
        ...
    ]
})
export class AuthModule { }

However, this alone was not sufficient as OAuthModule's DefaultOAuthInterceptor needed access to OAuthModuleConfig, which in turn required data from app.config.json. To resolve this dependency chain, I implemented the following workaround:

// Using HttpXhrBackend service instead of HttpClient to avoid injection issues
@Injectable()
export class AppConfigLoader {

    private _config: AppConfig | undefined;

    public constructor(
        private readonly httpClient: HttpXhrBackend
    ) { }

    public get config(): AppConfig {
        if (!this._config) {
            throw new Error('config is not loaded');
        }
        return this._config;
    }

    public async load(): Promise<void> {
        const request = this.createJsonRequest<AppConfig>(`${window.location.origin}/app.config.json`);
        this._config = await this.httpClient.handle(request).pipe(
            filter(event => event instanceof HttpResponse),
            map((response: any) => response.body)
        ).toPromise();
    }

    private createJsonRequest<T>(url: string): HttpRequest<T> {
        return new HttpRequest<T>('GET', url, { headers: new HttpHeaders({ 'content-type': 'application/json' }) });
    }
}

Answer №4

I encountered a similar issue and found that the OAuthModuleConfig needs to be configured synchronously. This means that the settings must be loaded before creating the OAuthModuleConfig, ideally in a factory function.

To address this, I made sure to fetch the settings prior to bootstrapping the AppModule.

Main.ts:

fetch('/assets/config.json')
.then(response => response.json())
.then(config => {

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic([{ provide: APP_SETTINGS, useValue: config }])
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));
});

App.module.ts:

Within the module declaration:

....
providers: [
{ provide: OAuthModuleConfig, useFactory: authConfigFactory, deps: [APP_SETTINGS] },
],
bootstrap: [AppComponent]
.....


export function authConfigFactory(settings: AppSettings): OAuthModuleConfig {
return {
resourceServer: {
    allowedUrls: settings.protectedUrls,
    sendAccessToken: true,
   }
 };
}

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

Transforming a Typescript class instance into a JavaScript object

Here is the code snippet I am working with: class A{ test: string constructor(test: string){ this.test = test } } const a = new A("hi") console.log(a) This is what the output looks like: A { test: 'hi' } However, when at ...

Can Angular 4 support the use of a "template reference variable"?

Trying to understand how to utilize a template reference variable within a .pug template. For instance: div(#var) results in an error: compiler.es5.js:1694 Uncaught Error: Template parse errors: There is no directive with "exportAs" set to "#var" (" ... ...

What is the best way to transform a string into a template reference object?

In the process of setting up ngTemplateOutlet within *ngFor, I encountered an issue similar to that outlined in the code snippet below. <ul> <li *ngFor="let item of list"> <ng-container [ngTemplateOutlet]="item.type"></ng- ...

Error: Unable to locate class "com.kotlin20test.Hilt_MyApp" due to java.lang.ClassNotFoundException

I encountered an issue with Hilt while trying to inject a retrofit interface using Hilt. Below is the error message: java.lang.ClassNotFoundException: Didn't find class "com.kotlin20test.Hilt_MyApp" on path: DexPathList[[zip file "/data/app/com.exam ...

Item removed from the cache despite deletion error

Currently, I am utilizing Angular 8 along with @ngrx/data for handling my entities. An issue arises when a delete operation fails (resulting in a server response of 500), as the entity is being removed from the ngrx client-side cache even though it was not ...

Creating a clickable link within an element using router link in Angular 2

I'm facing a challenge where I want to include a router link on a div element, but also have an anchor or button inside that div to open a menu. Due to the router link, the menu cannot be opened as expected - instead, clicking on the button redirects ...

ordering an array based on a boolean property in TypeScript

I'm currently working with angular 10 and I need help figuring out how to sort this array: var dic = [ { state: false, id: 1 }, { state: true, id: 2} , { state: false, id: 3 }, { state: true, id: 4 }, { state: false, id: 5 } ] My goal is to ...

Are you facing a version issue when trying to install npm packages like [email protected] and [email protected]?

Previously, I encountered unmet dependencies in npm installation. To resolve this issue, I referred to this helpful discussion. However, now I am facing problems related to npm deprecated versions: [email protected] and [email protected] when try ...

Having trouble getting Jest to manually mock in Nestjs?

When setting up a mock service like this: // /catalogue/__mock__/catalogue.service.ts export const CatalogueService = jest.fn().mockImplementation(() => { return { filterRulesFor: jest.fn().mockImplementation((role: Roles): Rule[] => rules.filt ...

Ensuring the correct class type in a switch statement

It's been a while since I've used Typescript and I'm having trouble remembering how to properly type guard multiple classes within a switch statement. class A {} class B {} class C {} type OneOfThem = A | B | C; function test(foo: OneOfThe ...

Touched the Force of ngModel

I am looking to make the required fields show in red in the angular material when the "Submit" button is clicked. To achieve this, I need to trigger the input to be marked as touched. <div class="formRow"> ...

Unleashing the Power of Angular 2's Reactive Form Isolation Feature

I am new to reactive forms and I need help with isolating scope when using FormArray in Angular. In the code below, I have a room type where I can add or remove form groups. When I select a value for the room type in the drop-down, it should populate the c ...

angular 2 updating material table

Issue at Hand: I am facing a problem with the interaction between a dropdown menu and a table on my website. Imagine the dropdown menu contains a list of cities, and below it is a table displaying information about those cities. I want to ensure that whe ...

Is there a way to modify component data in Angular 5 from another component?

I have a dashboard setup where the main component stays consistent across all pages, while another component, the load component, changes based on the router-outlet. My goal is to hide the "manage load" button in the dashboard component if I delete all loa ...

Ensuring that the designated key of an object is an extension of Array

What is the best way to ensure that the specified keyof a type extends an array? Consider the following type example: type TestType = { arrayKey: Array<number>, notArrayKey: number } There is also a function that accesses the specified key ...

Mastering Angular Reactive Forms - Harnessing the Power of FormArray with Multiple FormGroup Control Arrays

Trying to replicate my model in a reactive form has proven challenging. I've organized FormGroups to structure the data according to my model, but I'm struggling with setting up the component to properly display the values. This suggests that the ...

What is the best way to resolve the unusual resolution issue that arises when switching from Next.js 12 to 13

Previously, I created a website using nextjs 12 and now I am looking to rebuild it entirely with nextjs 13. During the upgrade process, I encountered some strange issues. For example, the index page functions properly on my local build but not on Vercel. ...

"Encountering a persistent issue with Angular POST requests to Spring Security JWT login consistently

I have implemented a stateless Spring Security application that utilizes JWT tokens for authentication. Upon launching the application, I attempted to login through Postman using credentials that were stored in the database via a POST method (I tried both ...

Experimenting with key directive in the newest version of Angular (9)

I'm currently testing a directive, but I am facing an issue where the length of the value in the directive is always undefined. What could be causing this problem? @Directive({ selector: '[evAutoTab]' }) export class EvAutoTabDirective { ...

Ways to pass data to a different module component by utilizing BehaviourSubject

In multiple projects, I have used a particular approach to pass data from one component to another. However, in my current project, I am facing an issue with passing data from a parent component (in AppModule) to a sidebar component (in CoreModule) upon dr ...