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

What is the reason TypeScript does not display an error when assigning a primitive string to an object String?

From my understanding in TypeScript, string is considered as a primitive type while String is an object. Let's take a look at the code snippet below: let s: string = new String("foo"); // ERROR let S: String = "foo"; // OK It's interesting to ...

Adal TypeScript Document

Recently, I've been experimenting with the TypeScript version of adal.js. As part of my setup process, I'm referring to this link to install adal.ts. However, after executing the command: npm install adal-typescript --save a new "node_modules" ...

Include token in src tag requests Angular version 8

In the process of developing a website, I have encountered a challenge. I am creating a platform where users can access another website I am currently working on after they log in. Once authorized, users receive a JWT token which is sent in the header with ...

The MatTableDataSource provides a promise that includes approximately 7000 rows of data

When attempting to load a large amount of data into a MatTableDataSource, I am facing an issue. I would like to display a loader until the data is fully set, but I am unsure of when that happens. I attempted to use a promise like this: return new Promise(r ...

Using TypeScript to pass a callback function to labelFormatter in the legend of a Highcharts chart

I am currently experimenting with integrating HighCharts into an Angular2 project using TypeScript. My goal is to customize the appearance of the legend text, adding an image next to it. I've found that HighCharts provides a labelFormatter property w ...

leveraging third party plugins to implement callbacks in TypeScript

When working with ajax calls in typical javascript, I have been using a specific pattern: myFunction() { var self = this; $.ajax({ // other options like url and stuff success: function () { self.someParsingFunction } } } In addition t ...

Issues encountered when integrating ag-grid-react with typescript

Despite extensive searching, I am unable to find any examples of utilizing ag-grid-react with TypeScript. The ag-grid-react project does have TypeScript typing available. In my React app, I have installed ag-grid-react: npm i --save ag-grid ag-grid-react ...

Tips for navigating the material ui Expanded attribute within the Expansion Panel

After looking at the image provided through this link: https://i.stack.imgur.com/kvELU.png I was faced with the task of making the expansion panel, specifically when it is active, take up 100% of its current Div space. While setting height: 100% did achi ...

What kind of null/undefined is being assumed?

system details: Visual Studio Code Version: 1.47.3 Typescript Version: 4.0.0-dev.20200727 tsconfig.js: "strict": true, code example: let x = null; // x is any type let y = x; // x is null type(why? x is any type on top), y is null type x = 1; / ...

Can you explain how to utilize multiple spread props within a React component?

I'm currently working in TypeScript and I have a situation where I need to pass two objects as props to my React component. Through my research, I found out that I can do this by writing: <MyComponent {...obj1} {...obj2} /> However, I'm fa ...

What is the best way to extract information from an observable stream?

Within my codebase, there exists an observable that I have defined as: public selectedObjectsIds$ = of(); In addition, there is another stream present: this.reportMode$.pipe( filter((state: ReportMode) => state === ReportMode.close) ) .subscribe(() ...

Wijmo encountered an error: Expected date but received different data

I have been encountering an issue with the Wijmo date picker. When I input a proper date format for the Wijmo date, sometimes it is accepted without any errors while other times an error message pops up. My code for setting the form value is: this.mfForm. ...

Vue is encountering difficulties resolving the index.vue file located in the parent directory

Having trouble importing a component from the path folder, I keep encountering an error message stating "Cannot find module './components/layout/Navbar'. Vetur(2307)". This is how I am attempting to import the component: import Navbar from "./c ...

The Proper Way to Position _app.tsx in a Next.js Setup for Personalized App Configuration

I've been working on a Next.js project and I'm currently trying to implement custom app configuration following the guidelines in the Next.js documentation regarding _app.tsx. However, I'm encountering some confusion and issues regarding the ...

Why is Axios not being successfully registered as a global variable in this particular Vue application?

Recently, I have been delving into building a Single Page Application using Vue 3, TypeScript, and tapping into The Movie Database (TMDB) API. One of the hurdles I faced was managing Axios instances across multiple components. Initially, I imported Axios ...

What is the best way to get my Discord bot to respond in "Embed" format using TypeScript?

I've been attempting to create a bot that responds with an embedded message when mentioned, but I'm running into some issues. Whenever I run this code snippet, it throws an error in my terminal and doesn't seem to do anything: client.on(&apo ...

The return type of Array.find is accurate, however, it contains an error

Trying to find a way to indicate the expected return type of Array.find() in TypeScript, I encountered an incompatibility warning. View code in playground class A { "type"="A" t: string; #a = 0 constructor(t: string) { ...

What is the method for opening the image gallery in a Progressive Web App (PWA)?

I am struggling to figure out how to access the image gallery from a Progressive Web App (PWA). The PWA allows me to take pictures and upload them on a desktop, but when it comes to a mobile device, I can only access the camera to take photos. I have tried ...

Errors can occur when using TypeScript recursive types

Below is a simplified version of the code causing the problem: type Head<T> = T extends [infer U,...unknown[]] ? U : never; type Tail<T> = T extends [unknown,...infer U] ? U : []; type Converter = null; type Convert<T, U extends Converter& ...

What Causes a Mongoose Query to Result in an Empty Array?

Hello, I have reviewed similar questions regarding the issue I am facing with developing an API. Despite trying different solutions, none seem to resolve my problem. When handling request and response payloads in my API, everything seems to be working fin ...