I want to do something like this in my routing.module.ts (see lines marked by-->)
export const routes: Routes = [
path: 'test/:action',
component: CreateComponent,
--> canActivate: ':action' == 'read' ? [Guard1] : [Guard1, Guard2],
data: {
--> screenAction: ':action' == 'read' ? 'read' : ':action',
}
]
I have to use the variable :action because it is used later in the router param. Thanks for your help !
Well, what you ask is possible but implementing it might be a little complex.
First of all, you need to define multiple routes instead of 1. Where you can apply the conditions you require.
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: AppComponent },
{
path: 'dummy1/:action',
component: Dummy1Component,
canActivate: [GuardRotaterGuard],
},
{
path: 'dummyx/:action',
component: Dummy2Component,
canActivate: [Guard1Guard],
},
{
path: 'dummyz/:action',
canActivate: [Guard1Guard, Guard2Guard],
component: Dummy2Component,
},
];
The route dummy1/:action is like a gateway route. Every request to dummy2 component should go from here. And then you need a decider/rotator guard that can decide and rotate the route depending on the route parameters.
It should look like below :
canActivate(
next: ActivatedRouteSnapshot,
statex: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
const { action } = next.params;
const state = { comesFrom: 'rotater' };
console.log("rotater",action);
if (action === 'read') { // you can decide whatever you want here
this.router.navigate(['/dummyx/read'], { state });
}else{
this.router.navigate(['/dummyz', action], { state }); // pass the action to the other route as a parameter
}
return true;
}
And here is a Stackblitz example in action.
Related
I want to stop loader from loading from few screens and therefore I applied ngIf at routes where loader isn't needed. Here is the code for app.component.ts :
<router-outlet>
<app-spinner></app-spinner>
<ngx-ui-loader *ngIf="!(currentRoute =='/dashboard' || currentRoute == '/vehicle/edit/')"></ngx-ui-loader>
</router-outlet>
app.component.html
this.currentRoute = "";
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.currentRoute = event.url;
}
});
I need to add * to vehicle/edit URL as there can be any vehicle ID while fetching the edit page like : /vehicle/edit/49042/1422, /vehicle/edit/49023/1421 and so on.
How to allow currentRoute accept /vehicle/edit/* ?
Ok, so to answer your question about route that accepts dynamic URLS/all URLS starting with /vehicle/edit/.
The "dummy" approach if you know that your nesting got limit, is to declare multiple routes with params, more-less like this:
const routes: Routes = [
{ path: '/vehicle/edit/', component: VehicleListComponent },
{ path: '/vehicle/edit/:id', component: VehicleEditComponent },
{ path: '/vehicle/edit/:parent/:id', component: VehicleEditComponent },
{ path: '/vehicle/edit/:grandparent/:parent/:id', component: VehicleEditComponent },
];
This will work, because Angular routing stops at very first matching path, so the order of your route declaration is important!
However, if you're dealing with very long nesting capability, better approach is to use custom route matcher:
import { UrlSegment } from '#angular/router';
const nestedCategoryMatcher = (url: UrlSegment[]) => {
// Check if this regex actually match your requirements
const regexMatcher = /^(vehicle\/edit)([\/][0-9]+.+)/;
if (!url.join('/').match(regexMatcher)) return null;
return ({ consumed: url });
}
const routes: Routes = [
{ path: '/vehicle/edit/', component: VehicleListComponent },
{ matcher: nestedCategoryMatcher, component: VehicleEditComponent },
];
And remember, that by using matcher, you will have to retrieve your "params" manually in your components by splitting URL into segments.
this.route.url
.subscribe(segments => {
const urlSegment: UrlSegment[] = (segments as UrlSegment[]);
console.log(urlSegment);
});
Is it possible to deactivate 2 same Paths with Guards which only redirect in Angular 9? I tried this but the first one is still active when LogoutGuard returns false:
{ path: '', redirectTo: 'login', pathMatch: 'full', canActivate: [LogoutGuard] },
{ path: '', redirectTo: 'home', pathMatch: 'full', canActivate: [LoginGuard] },
In this example LogoutRouter is returning false and LoginRouter true
So what happens is the following:
Router Event: NavigationStart platform-browser.js:88
NavigationStart(id: 1, url: '/') platform-browser.js:79
Object { id: 1, url: "/", navigationTrigger: "imperative", restoredState: null }
platform-browser.js:79
Angular is running in the development mode. Call enableProdMode() to enable the production mode. core.js:40465
Router Event: RoutesRecognized platform-browser.js:88
RoutesRecognized(id: 1, url: '/', urlAfterRedirects: '/login', state: Route(url:'', path:'') { Route(url:'login', path:'login') { Route(url:'', path:'') } } ) platform-browser.js:79
Object { id: 1, url: "/", urlAfterRedirects: "/login", state: {…} }
platform-browser.js:79
Router Event: GuardsCheckStart platform-browser.js:88
GuardsCheckStart(id: 1, url: '/', urlAfterRedirects: '/login', state: Route(url:'', path:'') { Route(url:'login', path:'login') { Route(url:'', path:'') } } ) platform-browser.js:79
Object { id: 1, url: "/", urlAfterRedirects: "/login", state: {…} }
platform-browser.js:79
Router Event: ChildActivationStart platform-browser.js:88
ChildActivationStart(path: '') platform-browser.js:79
Object { snapshot: {…} }
platform-browser.js:79
Router Event: ActivationStart platform-browser.js:88
ActivationStart(path: 'login') platform-browser.js:79
Object { snapshot: {…} }
platform-browser.js:79
Router Event: GuardsCheckEnd platform-browser.js:88
GuardsCheckEnd(id: 1, url: '/', urlAfterRedirects: '/login', state: Route(url:'', path:'') { Route(url:'login', path:'login') { Route(url:'', path:'') } } , shouldActivate: false) platform-browser.js:79
Object { id: 1, url: "/", urlAfterRedirects: "/login", state: {…}, shouldActivate: false }
platform-browser.js:79
Router Event: NavigationCancel platform-browser.js:88
NavigationCancel(id: 1, url: '/') platform-browser.js:79
Object { id: 1, url: "/", reason: "" }
platform-browser.js:79
Your problem
You always want to redirect from the url /
If the user is logged in, / should redirect to /home
If the user is logged out, / should redirect to /login
Your implementation
You have set up two routes with the same path. Each route has a different redirectTo property and canActivate guard. You expect that the guard is checked and whichever one returns true would then inform the router to redirect to the appropriate redirectTo path.
This relies on the Angular router checking specific conditions in a certain order, which is clearly not happening.
My solution
Add both guards to a single route.
{ path: '', component: AppComponent, canActivate: [LoginGuard, LogoutGuard] },
Instead of returning a straight true or false, the guard will return either true or the url to navigate to.
login.guard.ts
#Injectable({
providedIn: 'root'
})
export class LoginGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean | UrlTree {
const loggedIn = true; // TODO: implement
if (loggedIn) {
return true;
}
return this.router.parseUrl('login');
}
}
logout.guard.ts
#Injectable({
providedIn: 'root'
})
export class LogoutGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean | UrlTree {
const loggedIn = true; // TODO: implement
if (!loggedIn) {
return true;
}
return this.router.parseUrl('home');
}
}
The natural step from here is to create a base guard that is injected with the authenticated state it should allow through and the url to redirect to.
DEMO: https://stackblitz.com/edit/angular-jpzgww
I'm using angular 5 , I ve a long routing file :
const homeRoutes: Routes = [
{
path: 'home', component: HomeComponent,
children: [
{
path: 'registration',
component: RegistrationComponent,
children: [
{
path: 'synthese',
component: SyntheseComponent
},
{
path: 'queue',
component: QueueComponent,
children: [
{
path: 'queue-modal',
component: QueueModalComponent
},
{
path: 'confirm',
component: ConfirmComponent
}
]
}
]
},
{
...
And i want to pass data within the "registration" path .
As i was told , i need to write it like this : path: 'registration/:mydata',
and after that subscribe to data of ActivatedRoute
The problem that i'm not always passing data , it s only in some cases.
How may i make it with minimum of impact ??
You can use querystring parameters (AKA queryParams) instead of route parameters. You do not need to define query params in the route.
Here is an example of a relative href:
registration/?mydata=123
This is how you can define queryParams for a routerLink:
<a [routerLink]="['registration']" [queryParams]="{ mydata: 123 }">Go to Registration</a>
This is how you can read that param value:
myValue: any;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.myValue = params['mydata'];
});
}
I've seen several answers to this, but I'm not sure if they necessarily "fit" my scenario (I'm not trying to create parent/child routing relationships or anything like that). In my case I have a component that's responsible for adding new widgets, or editing existing widgets. My routes are defined like so:
const routes: Routes = [
{
path: 'widget/add',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
{
path: 'widget/:id/edit',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
];
path is a string so it can't accept an array of values or anything like that. Is there a way to specify multiple paths for a single route so I don't have to duplicate the other parts of the route (the component, canActivate, and data parts)?
Note: The paths cannot be changed due to application requirements (i.e. I can't just make a single manage path).
Edit: My ManageWidgetComponent already has the correct logic for handling Create vs. Edit. That part's fine.
I think you could do something like this :
const routes: Routes = ['widget/add', 'widget/:id/edit'].map(path => {
return {
path: path,
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
};
});
you can use this format :
{
path: 'widget/:id/add-edit',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
and in ManageWidgetComponent use fallowing code to check if there is a value for "id"? if there is no value for id so you are adding a new user and if there is a value for "id" so you are editing a user.
this.route.params.subscribe((params: any) => {
if (params.id) {
//////you are editing a user
}else{
///// you are adding a new user
}
}
);
By convention, there is a one to one relationship between a route and a path (they are basically the same thing), So you can't have different paths for a single routes.
But you can have different paths that loads the same component (and that's what you're doing in the example above)
A way to solve this problem would be :
{
path: 'widget/:id',
component: ManageWidgetComponent,
canActivate: [CanActivateViaRoleGuard],
data: { roles: ['Admin', 'User'] }
},
Then you can navigate to widget/add or widget/123
And in ManageWidgetComponent:
this.route.params.subscribe((params) => {
if (params.id === 'add') {
// Add new user
} else {
// Edit user with id = +params.id
}
});
I hope this is not duplicated.
I have this component to Login with a user, that calls a service and gives me a Token.
When the login is success I save the token to localStorage and I get the user Data with getUserData service and save it to a localStorage with saveUderData service.
onLoginSuccess(token: Token) {
this.authService.saveToken(token);
this.authService.getUserData().subscribe(
userData => this.onGetUserDataSuccess(userData)
);
}
onGetUserDataSuccess(userData: UserDataModel) {
this.authService.saveUserData(userData);
this.router.navigate(['/'])
}
The problem is that when I go to the main component the data is not
loaded and I have to refresh the page.
I have this in my main component.
if (localStorage.getItem('_UserToken') && localStorage.getItem('_UserData') && (!this.authService.userToken || !this.authService.userToken.access_token)) {
this.authService.saveToken(JSON.parse(localStorage.getItem('_UserToken')));
this.authService.saveUserData(JSON.parse(localStorage.getItem('_UserData')));
this.userData = this.authService.userData;
}
with this.userData I get the data.
I solved it in another component with Resolve but here I can't do it because i don't know how.
I tried this in onLoginSuccess(token: Token)
this.route.data.subscribe(
userData => console.log(userData['user'])
);
but the userData is undefined
here my routes.
const appRoutes: Routes = [
{path: '', canActivate: [AuthGuardService], data: { title: 'Inicio'}, resolve: {user: UserDataResolve},
children: [
{path: '', component: HomeComponent},
{path: '', component: MenuComponent},
]},
{path: 'login', component: LoginComponent, data: { title: 'Iniciar Sesión'} },
];
and here is my Resolve
#Injectable()
export class UserDataResolve implements Resolve<UserDataModel> {
constructor(private authService: AuthService) {}
resolve(route: ActivatedRouteSnapshot): Observable<UserDataModel> {
return this.authService.getUserData();
}
}
I hope you can understand me, my english is not the best. thanks =).
Try using a getter instead of a simple property in your component:
get userData(): string {
this.authService.userData;
}
That way any time changes are detected it will get the current userData from your service.