Accessing a data property within an Angular2 route, no matter how deeply nested the route may be, by utilizing ActivatedRoute

Several routes have been defined in the following manner:

export const AppRoutes: Routes = [
  {path: '', component: HomeComponent, data: {titleKey: 'homeTitle'}},
  {path: 'signup', component: SignupComponent, data: {titleKey: 'SIGNUP_FORM.TITLE'}},
  {path: 'signin', component: SigninComponent, data: {titleKey: 'SIGNIN_FORM.TITLE'}},
  {path: 'sendpasswordresetinformation', component: SendPasswordResetInformationComponent},
  {path: 'password/reset/:userAccountToken', component: PasswordResetComponent},
  {
    path: 'dashboard', component: DashboardComponent, children: [
    {path: '', component: DashboardSummaryComponent},
    {
      path: 'message', children: [
      {path: '', component: MessageSummaryComponent, data: {titleKey: 'MESSAGE_LIST.TITLE'}},
      {path: 'conversation/:otherId', component: MessageConversationComponent, data: {titleKey: 'XXX'}}]
    },
    {
      path: 'useraccount', component: UserAccountComponent, children: [
      {
        path: '',
        component: UserAccountSummaryComponent,
        data: {titleKey: 'XXX'},
        resolve: {
          userAccount: UserAccountResolve
        }
      },
      {path: 'address', component: UserAccountAddressComponent, data: {titleKey: 'ADDRESS_FORM.TITLE'}},
      {path: 'email', component: UserAccountEmailComponent, data: {titleKey: 'EMAIL_FORM.TITLE'}},
      {
        path: 'emailnotification',
        component: UserAccountEmailNotificationComponent,
        data: {titleKey: 'EMAIL_NOTIFICATION_FORM.TITLE'}
      },
      {path: 'firstname', component: UserAccountFirstNameComponent, data: {titleKey: 'FIRST_NAME_FORM.TITLE'}},
      {path: 'password', component: UserAccountPasswordComponent, data: {titleKey: 'PASSWORD_FORM.TITLE'}}]
    }]
  }];

Some of these routes are nested within others.

The goal is to access the titleKey property under the data regardless of the route's level of nesting.

Attempts have been made as shown below:

export class AppComponent implements OnInit {

  constructor(private translate: TranslateService,
              private sessionService: SessionService,
              private titleService: Title,
              private activatedRoute: ActivatedRoute) {
    let userLang = 'fr';
    translate.use(userLang);
    moment.locale(userLang);
  }

  ngOnInit() {
    this.sessionService.reloadPersonalInfo();
  }

  setTitle($event) { 
    this.translate.get(this.activatedRoute.snapshot.data['titleKey'])
     .subscribe(translation=>this.titleService.setTitle(translation));
  }
}

However,

this.activatedRoute.snapshot.data['titleKey']
remains undefined.

If anyone could provide guidance on how to retrieve a property from the route's data regardless of its nesting level, it would be greatly appreciated.

Edit: After reviewing the official Angular documentation for ActivatedRoute, an attempt was made using the map operator on the data property of the ActivatedRoute instance in the following way:

@Component({
  selector: 'app',
  template: `
              <section class="container-fluid row Conteneur">
                 <appNavbar></appNavbar>
                 <section class="container">
                    <router-outlet (activate)="setTitle($event)"></router-outlet>
                 </section>
              </section>
              <section class="container-fluid">  
                <appFooter></appFooter>
              </section>
              `,
  directives: [NavbarComponent, FooterComponent, SigninComponent, HomeComponent, ROUTER_DIRECTIVES]
})
export class AppComponent implements OnInit {

  constructor(private translate: TranslateService,
              private sessionService: SessionService,
              private titleService: Title,
              private activatedRoute: ActivatedRoute) {
    let userLang = 'fr';
    translate.use(userLang);
    moment.locale(userLang);
  }

  ngOnInit() {
    this.sessionService.reloadPersonalInfo();
  }

  setTitle($event) {
    this.activatedRoute.data.map(data=>data['titleKey'])
      .do(key=>console.log(key))
      .switchMap(key=>this.translate.get(key))
      .subscribe(translation=>this.titleService.setTitle(translation));
  }
}

Unfortunately, the variable key always ends up being undefined...

Answer №1

We encountered a similar issue and opted for a different approach. Instead of constantly monitoring data emissions on the ActivatedRoute, we decided to subscribe to the events Observable directly from the router:

import { Component } from "@angular/core";
import { NavigationEnd, Router } from "@angular/router";

declare var module: any;

@Component({
    moduleId: module.id,
    selector: "app-layout",
    templateUrl: "main-layout.component.html"
})
export class LayoutComponent {
    titleKey: string;

    constructor(private router: Router){}

    ngOnInit() {
        this.router.events
            .filter((event: any) => event instanceof NavigationEnd)
            .subscribe(() => {
                var root = this.router.routerState.snapshot.root;
                while (root) {
                    if (root.children && root.children.length) {
                        root = root.children[0];
                    } else if (root.data && root.data["titleKey"]) {
                        this.titleKey = root.data["titleKey"];
                        return;
                    } else {
                        return;
                    }
                }
            });
    }
}

It's important to note that we're utilizing the value in a component positioned at the top level but requires data from the deepest child route. With some tweaking, you can adapt this into a service that triggers events whenever the titleKey undergoes a change.

We hope this solution proves beneficial.

Answer №2

I recently discovered a fantastic tutorial that provided a clear solution: .

After implementing the solution from the tutorial above, I also utilized the ng2 translate service to convert the specified titleKeys in my route data into accurate labels:

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: ['../styles/bootstrap.scss'],
  template: `<section class="container-fluid row Conteneur">
                 <app-navbar></app-navbar>
                 <section class="container">
                    <router-outlet></router-outlet>
                 </section>
              </section>
              <section class="container-fluid">  
                <app-footer></app-footer>
              </section>`
})
export class AppComponent implements OnInit {

  constructor(private translate: TranslateService,
              private sessionSigninService: SessionSigninService,
              private titleService: Title,
              private router: Router,
              private activatedRoute: ActivatedRoute) {
    let userLang = 'fr';
    translate.use(userLang);
    moment.locale(userLang);
  }

  ngOnInit() {

    this.router.events
      .filter(event => event instanceof NavigationEnd)
      .map(() => this.activatedRoute)
      .map(route => {
        while (route.firstChild) route = route.firstChild;
        return route;
      })
      .filter(route => route.outlet === 'primary')
      .mergeMap(route => route.data)
      .mergeMap(data => this.translate.get(data['titleKey']))
      .subscribe(translation => this.titleService.setTitle(translation));
  }
}

Answer №3

Latest Update

If you want to implement the solution mentioned below in your Angular AppComponent,

   constructor(private translate: TranslateService,
          private sessionService: SessionService,
          private titleService: Title,
          private activatedRoute: ActivatedRoute) {

      this.router.events.subscribe((arg) => {
         if(arg instanceof NavigationEnd) { 
           console.log(this.getTitle(this.activatedRoute.snapshot));
         }
      });
   }

   getTitle = (snapshot) => {
     if(!!snapshot && !!snapshot.children && !!snapshot.children.length > 0){
      return this.getTitle(snapshot.children[0]);
     }
    else if(!!snapshot.data && !!snapshot.data['titleKey']){
      return snapshot.data['titleKey'];
    }
    else{
      return '';
    }
   }

It may seem a bit unconventional, but it gets the job done.

Previous Method

If you prefer the older method, try the following,

{
  path: 'conversation/:otherId', 
  component: MessageConversationComponent, 
  data: {titleKey: 'XXX'},
  // add below resolve
  resolve: {
            titleKey: MessageConversationResolve
  }      
}

Create a new service called MessageConversationResolve.ts and include it in providers as needed.

import { Injectable } from '@angular/core';
import { Router, Resolve,ActivatedRouteSnapshot } from '@angular/router';
import { Observable }             from 'rxjs/Observable';

@Injectable()
export class AdminDetailResolve implements Resolve<any> {
  constructor(private router: Router,
              private titleService: Title) {}

  resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
    // route.data will give you the titleKey property
    // console.log(route.data);
    // you may consume titleService here to setTitle

    return route.data.titleKey;
 }
}

The Angular version that supports the above solution is as follows:

Angular 2 version : 2.0.0-rc.5

Angular Router version : 3.0.0-rc.1

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide to releasing your Angular 6 library to npmjs along with a detailed README.md

I'm currently in the process of developing an Angular 6 library that I intend to share on npmjs. The library, named ng-as-multiselect-dropdown, is versioned on GitHub through the workspace project 'sample'. I used the Angular-CLI command &ap ...

Hold off on making any promises regarding Angular 2

Let me start by stating that I have gone through many responses and I am aware that blocking a thread while waiting for a response is not ideal. However, the issue I am facing is quite complex and not as straightforward to resolve. In my advanced project, ...

Creating Unique Layouts for Specific Routes in Next.js App Router

Issue with Layout Configuration I am facing a challenge in creating a unique layout for the /api/auth/* routes without including the components CustomNavbar and Footer from the main RootLayout. Despite my attempts, the main layout continues to be displaye ...

Using 'interface' declarations from TypeScript is unsupported in JS for React Native testing purposes

I have a ReactNative app and I'm attempting to create a test using Jest. The test requires classes from a native component (react-native-nfc-manager), and one of the needed classes is defined as follows export interface TagEvent { ndefMessage: N ...

What is the method for implementing an Inset FAB with Material UI in a React project?

Currently, I am working on a project that requires an "Inset Fab" button to be placed between containers. After referencing the Material Design documentation, I discovered that the component is officially named "Inset FAB". While I was able to find some tu ...

Error message in Angular 2: Unable to locate node module for import

Recently, I encountered an issue while attempting to utilize a specific node module called aws-api-gateway-client Although the installation was successful, I am facing difficulties with importing this module due to an error. Oddly enough, it works seamle ...

Click on the photo and drag your mouse outside of it to zoom out [Angular2]

HTML <div class="zoomPhoto" *ngIf="zoomed" (click)="unZoomPhoto()"> <img src="{{currentPhoto}}" [style.margin-left.px]="-(zoomedPhotoWidth/2)" [style.margin-top.px]="-(zoomedPhotoHeight/2)" #photo /> </div> CSS .zoomPhoto{ backg ...

I want to modify a class in Angular 8 by selecting an element using its ID and adjust the styling of a div

Is it possible to dynamically add and remove classes using getElementById in Angular 8? I need to switch from 'col-md-12' to 'col-md-6' when a user clicks on the details icon. I also want to modify the style of another div by setting d ...

Angular: Observing DOM changes through observables

I have been exploring ways to track changes in the DOM of my angular component. Utilizing observables to capture those changes into a typescript variable, although uncertain if that's the right approach. Here is how I've set it up: app.componen ...

Adding or removing an event listener for a document in Angular 2, without using the constructor

In my current project, I implemented a popup component with a movable template feature. The popup opens when the open() method is triggered and attaches movable listeners; then, it closes upon calling the close() method, but the listeners do not get remove ...

Begin the NextJS project by redirecting the user to the Auth0 page without delay

I am new to coding and currently working on a project using Typescript/NextJS with Auth0 integration. The current setup navigates users to a page with a login button that redirects them to the Auth0 authentication page. However, this extra step is unneces ...

Determine if lazy loading is functioning properly through programming

When it comes to Angular, ensuring lazy-loading remains intact can be tricky. Simply importing something from a lazy-loaded module into the app module can result in eager loading. That's why I make it a point to check for such errors during PR reviews ...

What are the steps to integrating standard Bootstrap into an Angular application?

There are times when the navbar class, collapse class, and dropdown toggle button may not be supported in an angular application even after installing Bootstrap with scripts. I am interested in finding out how I can ensure that every Bootstrap class is fu ...

Internationalization in Angular (i18n) and the powerful *ngFor directive

Within my Angular application, I have a basic component that takes a list of strings and generates a radio group based on these strings: @Component({ selector: 'radio-group', templateUrl: `<div *ngFor="let item of items"> ...

"An issue has been identified where TSLint and VSCode fail to display red underlines in

I am currently working on a single .ts file where I am experimenting with configuring tslint and tsconfig. To test the configuration, I intentionally added extra spaces and removed semicolons. Despite running the command tslint filename.ts and detecting e ...

Angular is throwing an error stating that the property 'json' cannot be found on the type 'Object'

After updating my Angular app to version 7 and switching to httpClient, I encountered the following error: Property 'json' does not exist on type 'Object' at line let act = data.json().find(x => x.ActivityId == activityId); I sus ...

Struggling to retrieve the value of a text field in Angular with Typescript

In the Angular UI page, I have two types of requests that I need to fetch and pass to the app.component.ts file in order to make a REST client call through the HTML page. Request 1: Endpoint: (GET call) http://localhost:8081/api/products?productId=7e130 ...

Ways to resolve the issue: ""@angular/fire"' does not contain the exported member 'AngularFireModule'.ts(2305) in an ionic, firebase, and

I am facing an issue while attempting to establish a connection between my app and a firebase database. The problem arises as I receive 4 error messages in the app.module.ts file: '"@angular/fire"' has no exported member 'AngularFi ...

Mastering the Art of Mocking Asynchronous Methods in Node.js Using Jest

I have the following files: |_ utils.js |_methods.js I am currently conducting unit testing for rest.js methods, where the content of the file is as follows: methods.js import Express from 'express' import { add_rec } from './utils' ...

Deploying Angular application on IIS results in errors specific to the IIS environment

I developed an application using .net 3.0 and Angular 8 in Visual Studio. I then updated Angular to version 10. While everything runs smoothly when I test the app locally, I encountered an error after publishing it on IIS: no such file or directory, open ...