Search code examples
angulartypescriptangular18

APP_INITIALIZER - TypeError: appInits is not a function Angular 18


I have a standalone angular 18 application with bootstraping:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideAnimations(), // required animations providers
    provideToastr(), // Toastr providers
    {
      provide: APP_INITIALIZER,
      useFactory: onAppStart,
      multi: true,
    },
  ],
};

my OnAppStart function:

export function onAppStart(): Promise<any> {
  const router = inject(Router);
  const userService = inject(UserService);
  return new Promise(async (resolve) => {
    await userService.InitUserProfile().pipe(
      tap(
        (r) => {
          if (r == undefined) {
            router.navigate(['/']);
          }
        },
        (e) => console.error(e)
      )
    );

    resolve(true);
  });
}

userService method

  public InitUserProfile(): Observable<UserDataModel | undefined> {
    return from(
      this.client
        .get(
          new GetUserDataRequest({
            evoCode: localStorage.getItem('user.evo') ?? '',
          })
        )
        .then((r) => {
          if (r.model.email) {
            this.userData.set(r.model);
            return r.model;
          }
          return undefined;
        })
    );
  }

After startup I get this error and i dont know why

main.ts:5 ERROR TypeError: appInits is not a function
    at _ApplicationInitStatus.runInitializers (core.mjs:31480:26)
    at core.mjs:33209:18
    at _callAndReportToErrorHandler (core.mjs:31571:20)
    at core.mjs:33207:12
    at _ZoneDelegate.invoke (zone.js:369:28)
    at Object.onInvoke (core.mjs:7048:25)
    at _ZoneDelegate.invoke (zone.js:368:34)
    at ZoneImpl.run (zone.js:111:43)
    at _NgZone.run (core.mjs:6900:24)
    at bootstrap (core.mjs:33163:17)

I want to load a user from server on startup of the application. This does not seem to work. Can you help me why?


Solution

  • You should define a factory function, that return another function which returns a promise. The definition will look like below.

    export const appConfig: ApplicationConfig = {
      providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideAnimations(), // required animations providers
        provideToastr(), // Toastr providers
        {
          provide: APP_INITIALIZER,
          useFactory: onAppStart,
          deps: [Router, UserService], // <- notice the deps
          multi: true,
        },
      ],
    };
    

    Then define the onAppStart to return a function that uses these dependencies, which are provided as function arguments:

    We can use firstValueFrom from rxjs to convert the observable to a promise:

    export function onAppStart(router: Router, userService: UserService): Promise<any> {
      return () => 
        firstValueFrom(
          userService.InitUserProfile().pipe(
            tap(
              (r) => {
                if (r == undefined) {
                  router.navigate(['/']);
                }
              },
              (e) => console.error(e)
            )
          )
        );
    }
    

    To further simplify the code you can make the service return a promise:

    public InitUserProfile(): Observable<UserDataModel | undefined> {
      return this.client
        .get(
          new GetUserDataRequest({
            evoCode: localStorage.getItem('user.evo') ?? '',
          })
        )
        .then((r) => {
          if (r.model.email) {
            this.userData.set(r.model);
            return r.model;
          }
          return undefined;
        });
    }
    

    Then the app initializer callback can be simplified to:

    export function onAppStart(router: Router, userService: UserService): Promise<any> {
      return () => userService.InitUserProfile().then((r) => {
        if (r == undefined) {
          router.navigate(['/']);
        }
      })
      .catch(err => console.log(err));
    }