Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get provider data before Angular application startup

My goal is to get "clientId" asynchronously from the AppConfigService and use it as the "GoogleLoginProvider" function's "clientId" before the app starts.

I could put it in an environment variable but, in my specific case, it is not an option.

I'm using Angular 8.

import { APP_INITIALIZER } from '@angular/core';

export function getGoogleClientId(appConfigService: AppConfigService) {
    return () => appConfigService.getGoogleClientId().toPromise().then((clientId) => {
        // service reliably returns clientId here
    });
}

const getGoogleId: Provider = {
    provide: APP_INITIALIZER,
    useFactory: getGoogleClientId,
    deps: [AppConfigService],
    multi: true
}

@NgModule({
    providers: [
        {
            provide: 'SocialAuthServiceConfig',
            useValue: {
                autoLogin: false,
                providers: [{
                    id: GoogleLoginProvider.PROVIDER_ID,
                    provider: new GoogleLoginProvider(clientId), //<-- How do I get the service's "clientId" here?
                }],
            } as SocialAuthServiceConfig
        }
    ]
})
export class AppModule {}
like image 304
Aaron Salazar Avatar asked Mar 28 '26 05:03

Aaron Salazar


2 Answers

Your problem is that you're using useValue to inject an object but you need to use useFactory to create a changeable, dependent value based on information unavailable before run time which allows dependencies (API service, config service, etc).

Then I suggest modifying the library you're using at this moment (angularx-social-login) to allow the behavior that you want.

However, I was reading the library code and I realized that they accept an object and a promise!

You can check It here

Hence, I create an example to handle promise and fetch our config from our server (API).

app.module.ts

export function AppConfigServiceFactory(
  configService: AppConfigService
): () => void {
  return async () => await configService.load();
}

@NgModule({
  imports: [BrowserModule, FormsModule, SocialLoginModule, HttpClientModule],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: AppConfigServiceFactory,
      deps: [AppConfigService],
      multi: true
    },
    {
      provide: "SocialAuthServiceConfig",
      useValue: new Promise(async resolve => {
        // await until the app config service is loaded
        const config = await AppConfigService.configFetched();

        resolve({
          autoLogin: false,
          providers: [
            {
              id: GoogleLoginProvider.PROVIDER_ID,
              provider: new GoogleLoginProvider(config.googleClientId)
            }
          ]
        } as SocialAuthServiceConfig);
      })
    }
  ]
})
export class AppModule {}


app.config.service

export class AppConfigService {
  static config: AppConfig | null = null;

  constructor(private api: ApiService) {}

  static configFetched(): Promise<AppConfig> {
    return new Promise(async resolve => {
      // wait for the app config service is loaded (after 3000 ms)
      const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
      const waitFor = async function waitFor(f) {
        // check each 500 ms
        while (!f()) await sleep(500);
        return f();
      };
      await waitFor(() => AppConfigService?.config); 

      resolve(AppConfigService.config);
    });
  }

  async load(): Promise<AppConfig> {
    try {
      // simulating HTTP request to obtain my config
      const promise = new Promise<AppConfig>(resolve => {

        // after 3000 ms our config will be available
        setTimeout(async () => {
          const config: AppConfig = await this.api.getConfig().toPromise();
          AppConfigService.config = config;
          resolve(config);
        }, 3000);

      }).then(config => config);

      return promise;
    } catch (error) {
      throw error;
    }
  }
}

My complete solution is here on stackblitz.

like image 144
bjdose Avatar answered Mar 29 '26 22:03

bjdose


i developed another solucion using @bjdose answer

as angularx-social-login can receive a Promise<SocialAuthServiceConfig>

However, I was reading the library code and I realized that they accept an object and a promise!

make a function that returns a Promise with the config, first get the necessary data from your backend in my case the client id string and then return the config object

/**
 * return a Promise with *angularx-social-login* config object
 */
async function getGoogleLoginConfig(): Promise<SocialAuthServiceConfig> {
  const clientId = await fetchGoogleClientId()
  return Promise.resolve(
    {
      autoLogin: true,
      providers: [
        {
          id: GoogleLoginProvider.PROVIDER_ID,
          provider: new GoogleLoginProvider(clientId)
        }
      ],
      onError: (err) => {
        console.error(err);
      }
    } as SocialAuthServiceConfig)
}
/**
 * get google client id from backend
 */
async function fetchGoogleClientId(): Promise<string> {
  let url = `${environment.auth.googleId}`
  let response = await fetch(url,
    {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
    })
  let data = await response.json()
  //change here to your backend response structure
  return data.client_id
}

lastly add angularx-social-login to your providers

  providers: [
    {
      provide: "SocialAuthServiceConfig",
      useValue: getGoogleLoginConfig()
    }
  ]
like image 21
Matias Martinez Avatar answered Mar 29 '26 23:03

Matias Martinez



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!