angular: Router's ActivatedRoute `data` returns empty {} if module is lazy

I’m submitting a…


[ ] Regression
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request

Current behavior

A subscription on ActivatedRoute’s data parameter outputs empty {} if the module is lazy.

Expected behavior

this.route.data.subscribe((data) => console.log(data)); must output route’s data object.

Minimal reproduction of the problem with instructions

app.routing.ts:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Routes } from '@angular/router';

const routes:Routes = [
    {
        path: 'terms',
        data: {
            title: 'Terms of use'
        },
        loadChildren: './Terms/terms.module#TermsModule'
    }
];

@NgModule({
    imports: [
        RouterModule.forRoot(routes)
    ],
    exports: [
        RouterModule
    ]
})

export class AppRouting {}

app.component.html

<top-panel></top-panel>
<router-outlet></router-outlet>

<a [routerLink]="['/terms']">Terms of use</a>

terms.routing.ts:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Routes } from '@angular/router';

import { TermsComponent } from './terms.component';

const routes:Routes = [
    {
        path: '',
        data: {
            title: 'Terms of use'
        },
        component: TermsComponent
    }
];

@NgModule({
    imports: [
        RouterModule.forChild(routes)
    ],
    exports: [
        RouterModule
    ]
})

export class TermsRouting {}

And here is top-panel component which is inside the app.component.html:

import { Component } from '@angular/core';
import { OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
    selector: 'top-panel',
    templateUrl: './top-panel.component.html',
    styleUrls: ['./top-panel.component.scss']
})

export class TopPanelComponent {

    constructor(private route: ActivatedRoute) {

    }

    ngOnInit() {
        this.route.data.subscribe((data) => console.log(data)); // just always empty {}
    }

}

(As you see I put data in both routings just for make sure subscription will work — well, it does not work).

Environment

Angular version: 4.4.3

Similar issues

#12767 have children array in the routing and issue is resolved with route.parent but I have no any child routes therefore I have no any parent reference.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 12
  • Comments: 21 (4 by maintainers)

Commits related to this issue

Most upvoted comments

data is available only with this hell-like construction:

import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { NavigationEnd } from '@angular/router';
import { ActivatedRoute } from '@angular/router';

@Component({
    selector: 'app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})

export class AppComponent {

    constructor(
        private router: Router,
        private route: ActivatedRoute
    ) {
        this.subscribeNavigationEnd();
    }

    subscribeNavigationEnd() {
        this.router
            .events
            .filter(e => e instanceof NavigationEnd)
            .map(() => this.route)
            .map(route => {
                if (route.firstChild) {
                    route = route.firstChild;
                }

                return route;
            })
            .filter(route => route.outlet === 'primary')
            .mergeMap(route => route.data)
            .subscribe(e => console.log(e)); // outputs my `data`
    }

}

And this is if you have children:

this.router
    .events
    .filter(e => e instanceof NavigationEnd)
    .map(() => {
        let route = this.activatedRoute.firstChild;
        let child = route;

        while (child) {
            if (child.firstChild) {
                child = child.firstChild;
                route = child;
            } else {
                child = null;
            }
        }

        return route;
    })
    .mergeMap(route => route.data)
    .subscribe(data => console.log(data));

one more solution

this._router.events.pipe( filter(event => event instanceof ActivationStart) ).subscribe(event => { this.routeData = event['snapshot'].data; });

This is still a problem. Why is this still closed and not reopened? What is the point of just creating more and more issues when we have a perfectly good one right here with a bunch of different workarounds already that shows to what length we have to go to access the information we need?!

Please make this a priority.

@romatron Thanks for the solution. I had to modify it a bit for my case, because when the route gets opened directly from the browsers address bar, there won’t be an ActivationStart event, but an ActivationEnd event, which works aswell. and i took the last segment of the route (when there are no children), because for every segment there’re events fired.

this.router.events.pipe( filter(event => event instanceof ActivationEnd && event.snapshot.children.length == 0) ).subscribe((event: ActivationEnd) => { console.log(event.snapshot.data); });

I finally found solution

  this.router.events.pipe(
    filter(event => event instanceof ResolveStart),
    map(event => {
      let data = null;
      let route = event['state'].root;

      while (route) {
        data = route.data || data;
        route = route.firstChild;
      }

      return data;
    }),
  ).subscribe(console.log);

@yozman you saved me, thank you very much !

@CAspeling because that would require to realize the inconsistencies in angular instead of rather having the community come up with 10 workarounds. It would all be easy for everyone if the likes of

this.route.snapshot.queryParamMap.get("paramName")

just did the job they were intended for, but instead for some reasons no one should have to care about the returned values are empty in some setups, and you need to poke around the internet, stumbling upon fixes with a half life of a few months because of the ever breaking syntax changes in lower-level functionalities. Will this ever stop ?

@jasonaden same problem, any update?

Hi @jasonaden

Here’s my reproduction: https://stackblitz.com/edit/angular-rakecj

If you click on the buttons, the data should never be {}, but always is.

No no, use rxjs 5.5+! Similar like this:

this.router.events.pipe(
  filter(event => event instanceof ChildActivationEnd),
  take(1),
).subscribe...

Try it:

this.router.events
    .filter(event => event instanceof ChildActivationEnd)
    .take(1)
    .subscribe(event => {
          console.log(event.snapshot.data)
     });

Nothing new about it, four months later ?

I have been racking my brain one week before getting into this issue. Considering there are a lot of important use cases (in my case: authentication with roles) this issue should be handled

Please if anyone is having problem with this, please check this: https://github.com/angular/angular/issues/31778#issuecomment-513784646

Since no reproduction added over the last week, I’m closing this issue. If this is still a problem, please create a new issue with the reproduction included.