I'm new to angular 2. In angular 1 i used ui-router and templateProvider to use different templates in one controller, for example:
templateProvider: function ($localStorage) {
if($localStorage.isAdmin) { //just an example, here could be any condition, like url has some params
return '<admin></admin>';
} else if($localStorage.isManager) {
return '<manager></manager>';
}
},
how can i achieve something similar in angular 2?
for example i have such routes:
{ path: 'customers/:id', component: CustomerDetailsComponent },
{ path: 'customers/:id/edit', component: CustomerDetailsComponent }
how can i in CustomerDetailsComponent check it?
like:
ngOnInit(): void {
this.activatedRoute.params.subscribe(params => {
if (params['edit']) { //how to add a condition? to split edit from show view
/*someTemplateLogic*/
}
});
}
is it possible to do?
so: two different templates for routes:
{ path: 'customers/:id', component: CustomerDetailsComponent },
{ path: 'customers/:id/edit', component: CustomerDetailsComponent }
with edit and without - how to check which route i have, and set such template in one component
<admin *ngIf="isAdmin"></admin
<manager *ngIf="!isAdmin"></manager>
ngOnInit(): void {
this.router.events
.filter(f => f instanceof NavigationEnd)
.forEach(e => this.isAdmin = e.url.endsWith('/edit'));
}
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);
});
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.
In my angular routing module, I am using a base navigation when its the default homepage, now on some condition, I want to decide which route to navigate. This is how my app routing looks.
{ path: '', redirectTo: 'home1', canActivate: [homepageGuard], pathMatch: 'full' },
{ path: 'home1', loadChildren: './lazyloadedmodule1' },
{ path: 'home2', loadChildren: './lazyloadedmodule2' },
{ path: 'home3', loadChildren: './lazyloadedmodule3' },
{ path: 'basehome', loadChildren: './lazyloadedmodule4' }
Now in my route guard, I am calling the subscription like this.
canActivate(): Observable<any> | boolean {
return this._myService.getCurrentApp().subscribe(
(r) => {
if(r === 'app1'){
//navigate to app1homepage
} else if (r === 'app2') {
//navigate to app2homepage
} else {
// navigate to base homepage
}
},
(e) => {
console.log(e);
return false;
}
);
}
This is how my service looks where I call the API.
public getCurrentApp(): Observable<any> {
return this.http.get(this.apiBaseUrl + 'getApp').pipe(
catchError((err: HttpErrorResponse) => {
return throwError(err.error.errorMessage);
}));
}
Firstly the routeguard is not taking the value as subscription, because I believe its expecting just a boolean, but i will be getting the response as string back. How do I make sure that during the first page load, its calling the API, and redirecting it accordingly?
I propose something like this. Use service and return true/false & navigate.
canActivate(): Observable<any> | boolean {
return this._myService.getCurrentApp()
pipe(
tap((response) => {
if(r === 'app1'){
this.router.navigate([...]);
} else if (r === 'app2') {
this.router.navigate([...]);
} else {
this.router.navigate([...]);
}
}),
map(() => false),
catchError((e)=> of(false))
)
}
}
Frankly writing, that's not the best solution for such issue. I believe that a better approach would be to create different AuthGuards for the different path which will do API request and check if it is allowed to go to a specific route. Then only return false or true.
Your guard should not subscribe to the observable, the router does that....
So you basically need to return an observable that returns a value of true or false.
If you're going to reroute, then you will obviously never return true.... try this....
canActivate(): Observable<boolean> {
return this._myService.getCurrentApp().pipe(
tap(r => {
if(r === 'app1'){
//navigate to app1homepage
} else if (r === 'app2') {
//navigate to app2homepage
} else {
// navigate to base homepage
}
}),
catchError(r => of(false))
);
}
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
}
});
How to use trying to use these kind of URLs with prefix/foo-bar-1123, prefix/foo-bar-1123/bazquux-123 with Angular 4 Router? The important bits are the numerical id's which I am trying to capture.
I have tried this, but the result is that I get a all data between // characted captured to single variable called categorySlug-:categoryId and subcategorySlug-:subcategoryId.
const routes = [{
path: 'prefix/:categorySlug-:categoryId',
component: MyComponent,
}, {
path: 'prefix/:categorySlug-:categoryId/:subcategorySlug-:subcategoryId',
component: MyComponent,
}]
export const MyRoute: ModuleWithProviders = RouterModule.forChild(routes)
Ultimately I'd like to end up with these kind of variables:
categorySlug=foo-bar
categoryId=1123
subcategorySlug=bazquux
subcategoryId=123
Is this something that the Router supports? Is is possible to extend Router to support these?
Create a custom UrlMatcher which can be used to implement matching and fetch id's from URL. This works at least in Angular 4.4.3.
const categoryUrlMatcher = (url: UrlSegment[], group: UrlSegmentGroup, route: Route): UrlMatchResult => {
if (url.length != 2) {
return null
}
if (url[0].path !== 'prefix') {
return null
}
const categoryMatch = url[1].path.match(/(\w+)-(\d+)$/)
if (categoryMatch) {
return {
consumed: url,
posParams: {
categorySlug: new UrlSegment(categoryMatch[1], {}),
categoryId: new UrlSegment(categoryMatch[2], {})
}}
}
return null
}
const routes: Routes = [
{
matcher: categoryUrlMatcher,
component: MyComponent,
}
]