1

I'm having a route /invoices/:invoiceId for an invoice detail view which needs to be protected with 2 conditions

  1. User needs to be authenticated --> Redirect to login page
  2. 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)));
};
5
  • I personally wouldn't blink at a second guard that checks the invoice ownership even if that technically duplicates the invoice fetching done by the resolver. The guard needs some data to process its responsibility (checking that the user has permission to access the invoice) while the resolver performs a completely separate function (retrieving data used by the component for a smoother loading experience). Of course, any performance downside goes away with client-side caching. Maybe InvoiceApiService.invoice can be memoized? Commented Aug 13, 2024 at 16:02
  • If you want to go the resolver route, you shold be able to return a UrlTree to a login/error page in the case of missing/inaccessible data. If you need NavigationExtras and you're on Angular 18, you can return RedirectCommand from the resolver instead. Commented Aug 13, 2024 at 16:37
  • @DM Unfortunately, it's not possible to do a redirect by returning a UrlTree from a resolver. The InvoiceComponent is loaded anyway and the invoice is becoming a UrlTree. But is possible to inject the Router and do router.navigate() from the resolver. Not the most clean solution, but maintaining memorized data is always a bit tricky. Commented Aug 14, 2024 at 8:51
  • Huh, I've been led astray, then. Apologies for sending you down the wrong path. I guess you're stuck between awkward routing in a resolver and having to cache/memoize the data in the service somehow. Commented Aug 14, 2024 at 14:20
  • 1
    @DM Correct. Anyway, I will try both ways to see which feels less bad. Thanks for your thoughts! Commented Aug 14, 2024 at 14:41

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.