I'm having a route /invoices/:invoiceId for an invoice detail view which needs to be protected with 2 conditions
- User needs to be authenticated --> Redirect to login page
- Invoice with given id belongs to the logged in user --> Redirect to homepage with error message
My idea was to use a guard for authentication state - and since the view needs the invoice data - a resolver for loading the invoice.
But, what if the data can't be loaded from the resolver? E.g. server error, authenticated user not allowed to load the given invoice id, ... What's the best approach here?
- Resolve the invoice to
nulland handle the redirect from the component? - Using another guard instead of a resolver to load the invoice and handle the error? But how to pass the successful data to the
InvoiceComponent?
Since guards are executed before any resolvers, i can't add another guard at the end.
Route
{
path: 'invoice/:invoiceId',
component: InvoiceComponent,
canActivate: [authGuard],
resolve: { invoice: invoiceResolver }
}
auth.guard.ts
export const authGuard: CanActivateFn = (_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const authenticationPolicy = inject(AuthenticationPolicy);
const router: Router = inject(Router);
return authenticationPolicy.isGranted().pipe(
catchError(() => {
const queryParams = { referrer: state.url };
const redirectUrl: UrlTree = router.createUrlTree(['login'], { queryParams });
return of(redirectUrl);
})
);
};
invoice.resolver.ts
export const invoiceResolver: ResolveFn<Observable<Invoice | null>> = (route: ActivatedRouteSnapshot) => {
const invoiceId = parseInt(route.paramMap.get('invoiceId') ?? '0', 10);
const invoiceApiService = inject(InvoiceApiService);
return invoiceApiService.invoice(invoiceId).pipe(catchError(() => of(null)));
};
InvoiceApiService.invoicecan be memoized?UrlTreeto a login/error page in the case of missing/inaccessible data. If you needNavigationExtrasand you're on Angular 18, you can returnRedirectCommandfrom the resolver instead.UrlTreefrom a resolver. TheInvoiceComponentis loaded anyway and the invoice is becoming aUrlTree. But is possible to inject theRouterand dorouter.navigate()from the resolver. Not the most clean solution, but maintaining memorized data is always a bit tricky.