question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Configurable Guards & Resolvers

See original GitHub issue

Feature Request

Relevant Package

This feature request is for @angular/router

Description

I want to use a guard or a resolver across the app in many places, but always with a slightly different configuration. E.g. a guard should redirect to different pages when some request fails or a resolver should load data from different endpoints.

The current solution is to either pass data in the route and let the guard/resolver access this data, or create a subclass of a guard / resolver for each specific configuration. However, there is no type-safety for route data and the data may be overwritten from a parent route when paramsInheritanceStrategy is active. Additionally, this can get really bloated when using several configured guards or resolvers:

// routes.ts
{
    path: 'page-a',
    component: PageAComponent,
    data: {
        authGuard: {
            failureRedirect: '/login'
        }
    },
    canActivate: [ AuthGuard ],
    resolve: {
        foo: FooContentResolver,
        bar: BarContentResolver
    }
},
{
    path: 'page-b',
    component: PageBComponent,
    data: {
        authGuard: {
            failureRedirect: '/signup'
        }
    },
    canActivate: [ AuthGuard ]
}
// resolvers

@Injectable()
abstract class ContentResolver implements Resolve<any> {
    // ...
}

@Injectable()
class FooContentResolver extends ContentResolver {

    readonly options: IContentResolverOptions = {
        url: '/api/foo',
        method: 'get'
    };

    // ...
}

@Injectable()
class BarContentResolver extends ContentResolver {

    readonly options: IContentResolverOptions = {
        url: '/api/bar',
        method: 'get'
    };

    // ...
}

Describe the solution you’d like

To solve this issue, I’m imagining something similar to the forRoot/ ModuleWithProviders approach, that would let you pass options to some injectable (like here, a guard/resolver). So the canActivate, canActivateChild and canLoad attributes of a Route could also accept some kind of InjectableWithProviders object next to the InjectionToken / string.

// routes.ts
{
    path: 'page-a',
    component: PageAComponent,
    canActivate: [ AuthGuard.withOptions({ failureRedirect: '/login' }) ],
    resolve: {
        foo: ContentResolver.load({
            url: '/api/foo',
            method: 'get'
        }),
        bar: ContentResolver.load({
            url: '/api/bar',
            method: 'get'
        })
    }
},
{
    path: 'page-b',
    component: PageBComponent,
    canActivate: [ AuthGuard.withOptions({ failureRedirect: '/signup' }) ]
}
const AUTH_GUARD_OPTIONS = new InjectionToken<IAuthGuardOptions>('auth-guard-options');

@Injectable()
class AuthGuard implements CanActivate {

    static withOptions(options: IAuthGuardOptions): InjectableWithProviders<AuthGuard> {
        return {
            injectable: AuthGuard,
            providers: [
                { provide: AUTH_GUARD_OPTIONS, useValue: options }
            ]
        };
    }

    // ...

}
const CONTENT_RESOLVER_OPTIONS = new InjectionToken<IContentResolverOptions>('content-resolver-options');

@Injectable()
class ContentResolver implements Resolve<any> {

    static load(options: IContentResolverOptions): InjectableWithProviders<ContentResolver> {
        return {
            injectable: ContentResolver,
            providers: [
                { provide: CONTENT_RESOLVER_OPTIONS, useValue: options }
            ]
        };
    }

    // ...

}

Describe alternatives you’ve considered

An alternative would be to use something similar to MAT_DIALOG_DATA used in @angular/material. We wouldn’t need a fully flexible InjectableWithProviders, but every guard / resolver could inject a common token, e.g. GUARD_OPTIONS / RESOVLER_OPTIONS that should be configurable in the route, e.g.:

// routes.ts
{
    path: 'page-a',
    component: PageAComponent,
    canActivate: [
        { guard: AuthGuard, options: { failureRedirect: '/login' } }
    ],
    resolve: {
        foo: {
            resolver: ContentResolver,
            options: {
                url: '/api/foo',
                method: 'get'
            }
        },
        bar: {
            resolver: ContentResolver,
            options: {
                url: '/api/bar',
                method: 'get'
            }
        }
    }
},
{
    path: 'page-b',
    component: PageBComponent,
    canActivate: [
        { guard: AuthGuard, options: { failureRedirect: '/signup' } }
    ]
}

I also tried to dynamically create a subclass in the static method and return it, but I assume this is not optimal for dependency discovery (and is currently failing with an error when using dependencies in the constructor of the guard: Error: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.):

@Injectable()
class AuthGuard implements CanActivate {

    static withOptions(options: IAuthGuardOptions) {

        @Injectable({ providedIn: 'root' })
        class ConfiguredAuthGuard extends AuthGuard {
            _options = options;
        }

        return ConfiguredAuthGuard
    }

    // ...

}

Related Issues

#18247 seems related to this, but suggests some more substantial changes to routes & guards in general (which I would like to see as well!)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:35
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
sg-gdarnellcommented, Jun 23, 2021

I don’t think the object-oriented solution that you mentioned is really that bad, but I suppose I could see it being a pain if you had like 50 of them.

That said, you can already accomplish this today using an InjectionToken with the factory option. See this StackBlitz for an example: https://stackblitz.com/edit/configurable-resolver?file=src%2Fapp%2Fapp.module.ts

Last I checked the factory option isn’t well-documented, but it’s very useful for cases like this.

0reactions
angular-automatic-lock-bot[bot]commented, Oct 21, 2022

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RunGuardsAndResolvers - Angular
Guards and/or resolvers will always run when a route is activated or deactivated. When a route is unchanged, the default behavior is the...
Read more >
Create Configurable Route Guards in Angular - Netanel Basal
Let's see how we can create one configurable guard that rules them all: We've created a higher-order function that takes a configuration and ......
Read more >
Guards and Resolvers (in Angular) - Joel Kingsley
A guard in Angular is a class with a method that decides whether a route can be loaded, activated or even deactivated. Auth...
Read more >
Explore Angular Router's runGuardsAndResolvers | juri.dev
runGuardsAndResolvers` allows you to take control over when the router executes your guards and resolvers.
Read more >
Router Guards • Angular - codecraft.tv
A route can be configured with multiple guards and the guards are checked in the order they were added to the route. We...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found