J'ai un peu de cornichon . J'utilise Route guard (implémentant l'interface CanActivate
) pour vérifier si l'utilisateur est autorisé à accéder à une route particulière:
const routes: Routes = [
{
path: '',
component: DashboardViewComponent
},
{
path: 'login',
component: LoginViewComponent
},
{
path: 'protected/foo',
component: FooViewComponent,
data: {allowAccessTo: ['Administrator']},
canActivate: [RouteGuard]
},
{
path: '**',
component: ErrorNotFoundViewComponent
}
];
Maintenant, cela fonctionne très bien pour protéger la route '/ protected/foo' de l'activer, mais je voudrais dire à l'utilisateur que la route à laquelle il tente d'accéder est interdite (similaire à 403 Forbidden que vous pouvez obtenir du serveur).
Le problème: Comment puis-je montrer à l'utilisateur cette vue d'erreur spéciale sans le rediriger vers une route d'erreur qui semble être l'option préférée par tant de sources que j'ai trouvées? Comment puis-je continue d’utiliser ma RouteGuard
sans charger réellement la route interdite, car si je vérifie l’accès dans ma FooViewComponent
et affiche un affichage différent, c’est un peu le point de défaite d’avoir RouteGuard
en premier lieu.
Idéalement, j'aimerais que ma RouteGuard
retourne non seulement false dans la méthode canActivate()
, mais remplace également le composant complètement par say ErrorForbiddenViewComponent
. Mais je ne sais pas comment le faire, ou est-ce que cela est possible? Des alternatives?
Voici à quoi ressemble ma garde de route:
import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {AuthService} from '../services/auth.service';
@Injectable()
export class RouteGuard implements CanActivate {
constructor(
private router: Router,
private auth: AuthService
) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const { auth, router } = this;
const { allowAccessTo } = next.data;
const identity = auth.getIdentity();
if (
identity &&
allowAccessTo.indexOf(identity.role)
) {
// all good, proceed with activating route
return true;
}
if (identity) {
// TODO show ErrorForbiddenViewComponent instead of redirecting
console.log('403 Forbidden >>', next);
}
else {
// not logged in: redirect to login page with the return url
const [returnUrl, returnQueryParams] = state.url.split('?');
console.log('401 Unauthorised >>', returnUrl, returnQueryParams, next);
router.navigate(['/login'], {queryParams: {returnUrl, returnQueryParams}});
}
return false;
}
}
Donc, j'empêche juste le chargement de la route, mais je ne redirige pas. Je ne redirige que les visiteurs non connectés à la route de connexion.
Raisonnement:
Quelqu'un at-il une sorte de solution pour cela? Je me demande également comment se fait-il qu'après que Angular 2+ ait été présent pendant si longtemps, personne ne s'était déjà trouvé dans ce genre de situation? Tout le monde est juste d'accord avec la redirection?
N'oubliez pas non plus que, bien que j'utilise actuellement la FooViewComponent
de manière synchrone, cela pourrait changer à l'avenir!
J'avais une fois travaillé sur le même problème.
Partage de mon stackblitz poc où j'ai créé -
/auth
route est fournie avec PermissionGuardService
garde)Le service de garde évalue le type d'utilisateur et traite la redirection/erreur en conséquence.
Les cas d'utilisation sont -
shows a toast with log in message
)shows a toast with unauthorised message
)show a toast with success messaage
)J'ai enregistré l'utilisateur dans la mémoire de stockage locale.
Faites-moi savoir si vous avez besoin d'un traitement spécial et je mettrai à jour la base de code.
À votre santé!
Votre RouteGuard peut injecter le service que vous utilisez pour les fenêtres modales et la fonction .canActivate()
peut afficher le mode modal sans redirection pour informer l'utilisateur sans perturber l'état actuel de l'application.
Pour cela, nous utilisons toastr et son wrapper angulaire, car il crée un modeless pop-up qui s'auto-supprime après tant de secondes, sans boutons OK/Annuler.
Après avoir regardé exemple angular2 fourni par Tarun Lalwani dans les commentaires de question et après avoir approfondi l'examen de article du chargeur de composants dynamiques sur la documentation angulaire , j'ai réussi à l'appliquer à mon code:
Je n'utilise plus ma RouteGuard
lors de la spécification des itinéraires:
{
path: 'protected/foo',
component: FooViewComponent,
data: {allowAccessTo: ['Administrator']}, // admin only
canActivate: [RouteGuard]
},
Au lieu de cela, j'ai créé RouteGuardComponent
spécial et voici comment je l'utilise:
{
path: 'protected/foo',
component: RouteGuardComponent,
data: {component: FooViewComponent, allowAccessTo: ['Administrator']}
},
C'est le code de RouteGuardComponent
:
@Component({
selector: 'app-route-guard',
template: '<ng-template route-guard-bind-component></ng-template>
// note the use of special directive ^^
})
export class RouteGuardComponent implements OnInit {
@ViewChild(RouteGuardBindComponentDirective)
bindComponent: RouteGuardBindComponentDirective;
// ^^ and here we bind to that directive instance in template
constructor(
private auth: AuthService,
private route: ActivatedRoute,
private componentFactoryResolver: ComponentFactoryResolver
) {
}
ngOnInit() {
const {auth, route, componentFactoryResolver, bindComponent} = this;
const {component, allowAccessTo} = route.snapshot.data;
const identity = auth.getIdentity();
const hasAccess = identity && allowAccessTo.indexOf(identity.role);
const componentFactory = componentFactoryResolver.resolveComponentFactory(
hasAccess ?
component : // render component
ErrorForbiddenViewComponent // render Forbidden view
);
// finally use factory to create proper component
routeGuardBindComponentDirective
.viewContainerRef
.createComponent(componentFactory);
}
}
En outre, cela nécessite une directive spéciale à définir (je suis sûr que cela peut être fait d'une autre manière, mais je viens d'appliquer cet exemple de composant dynamique à partir de documents angulaires):
@Directive({
selector: '[route-guard-bind-component]'
})
export class RouteGuardBindComponentDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Ce n’est pas une réponse complète à ma propre question (mais c’est un début), donc si quelqu'un fournit quelque chose de mieux (c’est-à-dire un moyen d’utiliser toujours canActivate
et la capacité de charger paresseux), je veillerai à en tenir compte.