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

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

Related

Get Dynamic Values in app.module.ts for Angular Application

I'm using the angularx-social-login package for social login purposes in my application. As per the documentation, I've to pass the google and Facebook client id within app.module.ts. But that's where my problem starts because I need to keep that dynamic and fetch the id's from cookies instead of passing the client id as a static string. To make it dynamic I tried to make a service and also export a function in app.module but nothing seems to work. When making an export function I can't pass cookies within it since there is no constructor to initialize the cookies and when passing the function through a service I cannot exclusively call it in app.module since I need to first create an instance of the service to call the service methods. Have been stuck with it for days now. Any sort of idea will be appreciated.
app.module.ts
let config = new AuthServiceConfig([
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") //need to make this dynamic
},
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider("xxxxxxxxxx") //need to make this dynamic
}
]);
export function provideConfig() {
return config;
}
service to fetch dynamic data
import { Injectable,Inject} from '#angular/core';
import { SocialLoginModule, AuthServiceConfig } from "angularx-social-login";
import { GoogleLoginProvider, FacebookLoginProvider } from "angularx-social-login";
import { CookieService } from 'ngx-cookie';
#Injectable({ providedIn: 'root' })
export class AppSocialLoginService {
public google;
public facebook;
public tempCookie;
constructor(private _cookiesService:CookieService) {
this.tempCookie = this._cookiesService.getObject('globalData');
}
getGoogle() {
if (this.tempCookie && this.tempCookie.hasOwnProperty('google_login_client_id')) {
this.google = this.tempCookie['google_login_client_id'];
return this.google;
}
}
getFacebook() {
if (this.tempCookie && this.tempCookie.hasOwnProperty('fb_login_id')) {
this.facebook = this.tempCookie['fb_login_id'];
return this.facebook;
}
}
provideConfig() {
let config = new AuthServiceConfig([
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider(this.getGoogle())
},
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider(this.getFacebook())
}
]);
return config;
} // cannot export this function in app.module since it's already in a class
}
Tried to make a function and export it to app.module but then the problem is I cannot initialize a constructor and get the cookies to pass inside the function
export function provideConfig() {
let config = new AuthServiceConfig([
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider("xxxxxxxxxxxxxxxxxxxxxxxxxx") //can't receive cookies in this
},
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider("xxxxxxxxxxxxxxxxxxx") //can't receive cookies in this
}
]);
return config;
}

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({
//...
})
}
}
});

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

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);
}
}

how to access object properties from TypeScript?

I'm new to Angular and TypeScript and just started working on a project using MEAN stack (MongoDB, Express, Angular, Node.js).
I created this mongoose module :
import * as mongoose from 'mongoose';
var Schema = mongoose.Schema;
const entrepriseSchema = new mongoose.Schema({
name: {type: String, unique: true, required : true},
telephone: Number,
logo: String,
web_site: String,
sites: [
{site_id: {type: Schema.Types.ObjectId, ref: 'Site'}}
]
});
const Entreprise = mongoose.model('Entreprise', entrepriseSchema);
export default Entreprise;
and this is my entreprise.component.ts :
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { FormGroup, FormControl, Validators, FormBuilder } from '#angular/forms';
import { ActivatedRoute } from '#angular/router';
import { EntrepriseService } from '../services/entreprise.service';
import { SiteService } from '../services/site.service';
#Component({
selector: 'app-entreprise',
templateUrl: './entreprise.component.html',
styleUrls: ['./entreprise.component.scss'],
providers: [EntrepriseService, SiteService]
})
export class EntrepriseComponent implements OnInit {
entreprise = {};
sites = [];
id: String;
constructor(private entrepriseService: EntrepriseService,
private siteService: SiteService,
private http: Http,
private route: ActivatedRoute) {
this.id = route.snapshot.params['id'];
}
ngOnInit() {
this.getEntrepriseById(this.id);
//not working
//console.log(this.entreprise.name);
//console.log(this.entreprise.sites);
//this.getSitesIn(this.entreprise.sites);
}
getEntrepriseById(id) {
this.entrepriseService.getEntreprise(id).subscribe(
data => this.entreprise = data,
error => console.log(error)
);
}
getSitesIn(ids) {
this.siteService.getSitesIn(ids).subscribe(
data => this.sites = data,
error => console.log(error)
);
}
}
when I try to display the properties of the returned from entreprise.component.html it works fine and displays all the properties :
<h3>{{entreprise.name}}</h3>
<div *ngFor="let site of entreprise.sites">
{{site.site_id}}
</div>
{{entreprise.logo}}
{{entreprise.web_site}}
but how can I access the same properties on the TypeScript side ?
The commented code in the EntrepriseComponent is what I'm trying to accomplish but it's not working since this.entreprise is type {} .
The Enterprise model/schema that you created in Mongoose in Node.js resides on the server side. If you want the TypeScript code on the UI to recognize the properties in Enterprise, you will have to create a class in your angular codebase.
Create a folder named, say, models at the same level as your services folder. (Optional)
Create two files named site.ts and enterprise.ts in the models folder created in the previous step (You can put these file at a different location if you want) with the following contents:
site.ts
export interface Site {
site_id?: string;
}
enterprise.ts
import { Site } from './site';
export interface Enterprise {
name?: string;
telephone?: string;
logo?: string;
web_site?: string;
sites?: Site[];
}
Now, inside the EntrepriseComponent file, add the following imports
import { Enterprise} from '../models/entreprise';
import { Site } from '../models/site';
And change the first lines inside the EntrepriseComponent file to
export class EntrepriseComponent implements OnInit {
entreprise: Enterprise = {};
sites: Site[] = [];
Now, the enterprise attribute will be of type Enterprise and you will be able to access the properties that we declared in the enterprise.ts file.
Update:
Also, you cannot console.log(this.enterprise.name) immediately after this.getEntrepriseById(this.id); in your ngOnInit() function. This is because the web service you are making to get the enterprise object would not have resolved when you are trying to log it to the console.
If you want to see the enterprise object in the console or you want to run some code that needs to run after the service call has resolved and the this.enterprise object has a value, the best place to do this would be your getEntrepriseById function. Change the getEntrepriseById function to
getEntrepriseById(id) {
this.entrepriseService.getEntreprise(id).subscribe(
data => {
this.enterprise = data;
console.log(this.enterprise.name);
// Any code to run after this.enterprise resolves can go here.
},
error => console.log(error)
);
}

Categories