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
null
and 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)));
};