Angular RouteReuseStrategy shouldReuseRoute methods future parameter doesn't provide the actual url - javascript

I'm facing the problem that the actual url isn't provided through the future parameter of the shouldReuseRoute method in my custom implementation of RouteReuseStrategy. The url property has the last url not the actual one. The private property _routerState of the future parameter has the right new one in its property url. Does anyone know any further on this or did I get something totally wrong ? Thanks for your help!
export class RoutingStrategy implements RouteReuseStrategy {
...
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
console.log(future);
}
}

ActivatedRouteSnapshot.url only contains the part of the URL that matched the route. I.e. if you have routes
const routes: Routes = [
{
path: '',
children: [
{
path: 'xxx',
children: [
{
path: 'yyy/:id',
...
and navigate to /xxx/yyy/3 then ActivatedRouteSnapshot.url for the innermost route will be an array containing UrlSegment objects for 'yyy' and '3'.
You can get the complete URL from ActivatedRouteSnapshot.pathFromRoot, which contains the snapshots of all routes leading up to this. From these you could construct the string URL for example like this:
export class RoutingStrategy implements RouteReuseStrategy {
...
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
const url = future.pathFromRoot.map(p => p.url.map(segment => segment.path)).flat().join("/");
console.log(url);
}
}

Related

Cannot navigate another component in Angular

I have a List and Details components in an application and I am trying to navigate to Details component by passing id parameter. However, there is not a reponse or error when calling the following method. I also share the routing.module:
routing.module
const routes: Routes = [
{
path: '',
component: ListComponent,
data: {...}
},
{
path: '/details/:id',
component: DetailsComponent,
data: {...}
}
];
list.component
constructor(private router: Router) {}
details(id) {
// the code hits here and get the id parameter correctly
this.router.navigate(['/details'], {
queryParams: { id: id }
});
}
details.component
constructor(private route: ActivatedRoute) { }
ngOnInit(): void {
this.route.paramMap
.subscribe(params => {
let id = +params.get('id');
});
}
So, what is wrong with this approach? The ngOnInit block of the details page is not fired.
In your route, you have specified '/details/:id' where ID is Router Param not a Query Param.
Thus, if you want to navigate to that url, use this instead:
ListComponent
this.router.navigate(['/details', id])
DetailsComponent
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const id = this.route.snapshot.params.id; // Fetch the ID from your
// current route "/details/:id"
}
or you can also do it this way
ngOnInit() {
this.route.params.subscribe(params => console.log(params.id))
}
More info on Angular Router Documentation
you have to add queryParamsHandling: 'merge' to your code
details(id) {
// the code hits here and get the id parameter correctly
this.router.navigate(['/details'], {
queryParams: { id: id },
queryParamsHandling: 'merge'
});
}
Hello in the app component.html
Please add
<router-outlet></router-outlet>

Angular Test Jest - TypeError: Cannot read property 'queryParams' of undefined

I've seen the same error in other posts but they didn't work for me.
I have an Angular component, where I need to read a queryParam from the url, for example in http://localhost:4200/sample-page?page=3 I want to stores the number 3 into a component local variable.
/**
* Set page by url parameter
*/
export const setPaginationMarkByUrlParam = (activatedRoute): number => {
// Get page param from Url to set pagination page
const pageParam = activatedRoute.snapshot.queryParams;
return pageParam.page ? Number(pageParam.page) : 1;
};
This function is in another file and I put as parameter the activeRoute, which comes from the ngOnInit of the component in which I want to get the queryParam.
ngOnInit() {
this.page = setPaginationMarkByUrlParam(this.activatedRoute);
}
This code works perfectly, but when the Jenkins pipeline runs the npx jest tests, I get the following message:
TypeError: Cannot read property 'queryParams' of undefined
My Spec.ts:
beforeEach(() => {
TestBed.configureTestingModule({
...,
providers: [
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) =>
fn({
pagingParams: {
predicate: 'id',
reverse: false,
page: 0
}
})
}
}
}
]...
it('Should call load all on init', () => {
// GIVEN
const headers = new HttpHeaders().append('link', 'link;link');
spyOn(service, 'query').and.returnValue(
of(
new HttpResponse({
body: [new DataSource(123)],
headers
})
)
);
// WHEN
comp.ngOnInit();
// THEN
expect(service.query).toHaveBeenCalled();
expect(comp.dataSources[0]).toEqual(jasmine.objectContaining({ id: 123 }));
});
The test fails in comp.ngOnInit(); function.
I don't have any kind of private variables, the activeRoute that comes as a parameter, I tried it with public and private.
Looking at both StackOverflow and GitHub Issues I have not been able to fix this problem.
Thank you very much!
While you are mocking data, you are not mocking snapshot on ActivatedRoute. You have three choices to accomplish this:
First, you should consider using an ActivatedRouteStub as described in the docs. This then makes is as easy as: activatedRoute.setParamMap({page: 3}); to set any queryParameter you want to set. This option requires more test code
Next option: this would mock the queryParameter of page on the ActivatedRoute with an Observable:
provide: ActivatedRoute, useValue: {
snapshot: of(queryParams: { page: 3 }),
}
If, for a reason not disclosed in your question, you do not need an Observable from your ActivatedRoute, this would be the alternate code:
provide: ActivatedRoute, useValue: {
snapshot: { queryParams: { page: 3 } }
}
Finally, the code you provided doesn't have a call to inject for the ActivatedRoute provider nor does it show the test component creation. So at a minimum ensure you are doing that as well:
Either:
fixture = TestBed.createComponent(...);
Or:
activatedRoute = TestBed.inject(ActivatedRoute);
If none of these suggestions solve your problem, put a a minimal StackBlitz that demonstrates the problem and we'll get it working.
Generally for configuring/mocking different RouteOptions when you have ActivatedRoute with Jest you should use:
createRoutingFactory from #ngneat/spectator/jest
and pass one or more of the RouteOptions properties like (params, queryParams, parent etc.) directly on its constructor
For example:
const createComponent = createRoutingFactory({
component: Component,
imports: [RouterTestingModule],
parent: {
snapshot: {
queryParamMap: convertToParamMap({
//...
}),
paramMap: convertToParamMap({
//...
})
}
}
});

Accessing route parameters in Angular 7/8 layout components and services?

I have a multitenant application that identifies tenants based on a route parameters called organization. I have various portions of the application that need to change behavior based upon the organization passed in through the route parameter, but I'm running into some issues accessing the parameter anywhere other than the component that the router has navigated to.
In multiple areas of my application, I've placed a variant of the following code:
export class OrganizationSidebarComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.params.subscribe(params => {
console.log('[OrganizationSidebarComponent] ID IS ' + params['organization']);
console.log(params);
});
this.route.paramMap.subscribe(paramMap => {
console.log('[OrganizationSidebarComponent] ID from ParamMap: ' + paramMap.get('organization'));
});
console.log('[OrganizationSidebarComponent] ID using snapshot paramMap: ' + this.route.snapshot.paramMap.get('organization'));
console.log('[OrganizationSidebarComponent] ID using snapshot params: ' + this.route.snapshot.params.organization);
}
}
However, when the pages are navigated to, the output looks like the following:
My routes are setup like so:
const routes: Routes = [
{
path: 'Organization',
component: OrganizationsLayoutComponent,
children: [
{
path: ':organization',
component: OrganizationDashboardComponent,
canActivate: [AuthGuard]
}
]
}
];
In a perfect world, I'd be able to access the values from some injected service so that I have a centralized fetching service that I can leverage to obtain the required information, but I'm simply having issues retrieving the route parameters from anywhere that isn't the component that is being navigated to.
How can I access this information?
I would suggest to do it in another way, (based on your problem statement) instead of reading the organization in many components and loading data, read that in the app.component as below
add this method in App.component.ts
mergeRouteParams(router: Router): { [key: string]: string } {
let params = {};
let route = router.routerState.snapshot.root;
do {
params = { ...params, ...route.params };
route = route.firstChild;
} while (route);
return params;
}
then in ngOnInit do this
this.router.events.subscribe((e) => {
if (e instanceof NavigationEnd) {
const mergedParams = this.mergeRouteParams(this.router);
console.log(mergedParams);
}
});
and of course add private router: Router to your constructor
Working stackblitz
https://stackblitz.com/edit/angular-merge-route-params
Url to check https://angular-merge-route-params.stackblitz.io/dictionary/dict/code
Solution 2
Another way to get all params is using route configuration
export const routingConfiguration: ExtraOptions = {
paramsInheritanceStrategy: 'always',
};
#NgModule({
imports: [RouterModule.forRoot(routes, routingConfiguration)],
exports: [RouterModule],
})
export class AppRoutingModule { }
now if you inject route: ActivatedRoute in the params or paramsMap you will all params from parents including this child route

Make generic object optional when mapping over object keys (in keyof)

I have problem with mapping in keyof. I'm trying to map over Routes type, but when I map over the object then it breaks conditional params for Route.
type Routes = {
'/home': {}
'/pages': {
pageId: number
}
}
type IRoute<RouteName, Params> = {
route: RouteName
} & ({} extends Params ? { params?: Params } : { params: Params })
type Router = {
[RouteName in keyof Routes]: IRoute<RouteName, Routes[RouteName]>
}
type Route = Router[keyof Router]
Here params should be required but TS ignores it:
const foo: Route = {
route: '/pages'
// Missing params: { pageId: number }
}
I need Route type with routeName and params. If params is a generic object then make it optional.
const foo3: IRoute<'/foo', {id: number}> = {
route: '/foo',
params: {
id: 1
}
}
const foo4: IRoute<'/foo', {}> = {
route: '/foo'
}
Here's my code. If you call IRoute it works as I expect. But when IRoute is called from mapping through in keyof it breaks and params is optional for all routes.
Here's a TS playground.
Change your condition from:
{} extends Params ?
to:
keyof Params extends never ?
See the TypeScript Playground.

How can I subscribe for a route change and get a parameter?

I've tried the sample on the official page but neither it works nor the other examples on other pages work me. There are no params neither of the router nor the route export. How can I subscribe and get the parameter change?
Neither can I use the RouteParams, I got an exception there is no provider for it.
Thank you!
How am I able to do this with the current version of angular 2?
private sub: any;
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['id']; // (+) converts string 'id' to a number
this.service.getHero(id).then(hero => this.hero = hero);
});
}
my code:
#RouteConfig([
{ path: '/:appleId/:bananaId/fruits/...', name: 'Fruits', component: FruitsComponent }
])
How Can I get the appleId or bananaId change in the FruitsComponent?
The this.route.params does not exist :( What should I use instead?
this is the most suitable to get parameter from url
import {ActivatedRoute} from '#angular/router';
constructor(private activatedRoute: ActivatedRoute) {}
this.activatedRoute.queryParams.subscribe(params => {
let id = = params['id'];
});
Try below one:
#RouteConfig([
{
path: '/:id', name: 'Fruits', component: FruitsComponent
}
])
ngOnInit() {
this.sub = this.route.snapshot.params['id'];
if(this.sub == 'appleId/:bananaId/fruits'){
// your code
}
}

Categories