Angular 17 - SSR Standalone - Routing FactoryProvider with async - NG04014

334 Views Asked by At

Introduction

Hello, I'm trying to configure my angular router and pass it different routes depending on whether or not we are authenticated.

Here is my code to do this:

app.routes.ts

export const authenticatedRoutes: Routes = [
  {
    path: 'test',
    component: DashboardPage,
  },
  {
    path: '**',
    redirectTo: 'test'
  }
];

export const unauthenticatedRoutes: Routes = [
  {
    path: 'test',
    component: HomePage,
  },
  {
    path: '**',
    redirectTo: 'test'
  }
];

export const provideRoutes = () => {
  const provider: Provider[] = [{
    provide: ROUTES,
    useFactory: async (oidcSecurityService: OidcSecurityService) => {
      const { isAuthenticated } = await firstValueFrom(oidcSecurityService.checkAuth())
      if (isAuthenticated) return authenticatedRoutes
      return unauthenticatedRoutes
    },
    multi: true
  }]
  return makeEnvironmentProviders(provider)
}

app.config.ts

import { provideRoutes } from '@/configurations'

export const appConfig: ApplicationConfig = {
  providers: [
    provideRoutes(),
    // others providers...
  ]
};

Research

When I run "ng build --watch --configuration development", I got this error

An unhandled exception occurred: NG04014: Invalid configuration of route ''. One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren

The problem comes from the async factory, this code works:

export const provideRoutes = () => {
  const provider: Provider[] = [{
    provide: ROUTES,
    useFactory: () => {
      return unauthenticatedRoutes
    },
    multi: true
  }]
  return makeEnvironmentProviders(provider)
}

But I have no idea how to solve this problem. Do you know what I should do?

2

There are 2 best solutions below

0
Matthieu Riegler On BEST ANSWER

Asynchronous providers are not supported by Angular (see issue).

You're probably looking for route guards, and more specifically : CanActivate

Your guard

export const yourGuardFunction: CanActivateFn = (
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot) => {
      // your  logic goes here
  }

Your route:

{
  path: '/your-path',
  component: YourComponent,
  canActivate: [yourGuardFunction],
}
0
Ugo Evola On

Thanks @Matthieu Riegler, I'm following up your message here, I found this solution with the guard canMatch:

const authGuard: CanMatchFn = async () => {
  const oidcSecurityService = inject(OidcSecurityService)
  const { isAuthenticated } = await firstValueFrom(oidcSecurityService.checkAuth())
  return isAuthenticated
}

export const authenticatedRoutes: Routes = [
  {
    path: '',
    component: DashboardPage
  }
];

export const unauthenticatedRoutes: Routes = [
  {
    path: '',
    component: HomePage
  }
];

export const routes: Routes = [
  {
    path: '',
    children: authenticatedRoutes,
    canMatch: [authGuard]
  },
  {
    path: '',
    children: unauthenticatedRoutes
  },
  {
    path: '**',
    redirectTo: ''
  }
]