Search code examples
angulartypescriptangular14

Angular dynamically loading API keys and passing it to custom npm package


What I'm trying to do

I have an angular application the imports a custom library as an npm package. I need to load the API keys dynamically from an API. Before I was loading the API keys from environment.ts file

I'm able to pass the key value to the library but the library doesn't wait for the api key value to arrive.

I've tried multiple ways to pass the value but finally was able to do it using this approach https://stackoverflow.com/a/66957293/29743045

How can I make the library wait for the api key to arrive before initializing other services that use the key value?

Main Application Code

main.ts

fetch(`${environment.apiUrl}/keys`)
  .then((response) => response.json())
  .then((config) => {

    platformBrowserDynamic([{ provide: STREAM_API_KEY, useValue: config.streamApiKey }])
      .bootstrapModule(AppModule)
      .catch((err) => console.error(err));
  })
  .catch((error) => {
    console.error('❌ Error fetching API key:', error);
  });

ChatModule

@NgModule({
  imports: [
    CommonModule,
    HttpClientModule,
    UpChatLibModule.forRoot({
      apiKey: '', // Placeholder, will be set in the constructor
      service: {
        provide: UpChatApiIntegrationService,
        useClass: ChatApiIntegrationService,
      },
    }),
  ],
  providers: [],
  declarations: [ChatComponent],
  exports: [UpChatLibModule],
})
export class ChatModule {
  constructor(@Inject(STREAM_API_KEY) private streamApiKey: string) {
    UpChatLibModule.forRoot({
      apiKey: this.streamApiKey,
      service: {
        provide: UpChatApiIntegrationService,
        useClass: ChatApiIntegrationService,
      },
    });
  }
}

Library Application

UpChatLibModule

export interface UpChatConfiguration {
  apiKey: string;
  service: Provider;
}


@NgModule({
  providers: [
    StreamService,
  ],
  declarations: [
    UpChatComponent,
  ],
  imports: [
    CommonModule,
    HttpClientModule,
  ],
  exports: [
    UpChatComponent,
  ],
})
export class UpChatLibModule {}

  static forRoot(
    configuration: UpChatConfiguration
  ): ModuleWithProviders<UpChatLibModule> {
      console.log('configuration', configuration);
    return {
      ngModule: UpChatLibModule,
      providers: [
        configuration.service,
        { provide: 'CHAT_CONFIG', useValue: configuration },
      ],
    };
  }
}

Solution

  • Got this working finally. Thanks to @Naren Murali for helping with the solution

    So as per Naren's solution from chat I'm loading the keys in the main.ts itself and then in the ChatModule I did the import with empty key. Then in the library module I used a BehaviourSUbject to hold the configuration and then subscribed it in the necessary services.

    main.ts

    fetch(`${environment.apiUrl}/keys`)
    .then((response) => response.json())
    .then((config) => {
    platformBrowserDynamic([
          {
            provide: UpChatLibModule,
            useValue: UpChatLibModule.forRoot({
              apiKey: config.streamApiKey,
              service: {
                provide: UpChatApiIntegrationService,
                useClass: ChatApiIntegrationService,
              },
            }),
          },
        ])
          .bootstrapModule(AppModule)
          .catch((err) => console.error(err));
      })
    .catch((error) => {
    console.error('❌ Error fetching API key:', error);
    });
    

    ChatModule

    imports: [
        CommonModule,
        HttpClientModule,
        TranslateModule,
        UpChatLibModule.forRoot({
          apiKey: '',
          service: {
            provide: UpChatApiIntegrationService,
            useClass: ChatApiIntegrationService,
          },
        }),
      ],
    

    UpChatLibModule

    export class UpChatLibModule {
      private static chatConfigSubject = new BehaviorSubject<UpChatConfiguration | null>(null);
     static forRoot(
        configuration: UpChatConfiguration
      ): ModuleWithProviders<UpChatLibModule> {
          console.log('ApiKey', configuration);
          this.chatConfigSubject.next(configuration);
        return {
          ngModule: UpChatLibModule,
          providers: [
            configuration.service,
            { provide: 'CHAT_CONFIG', useValue: this.chatConfigSubject.asObservable() },
          ],
        };
      }
    }