Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Router's ActivatedRoute data returns empty {} if module is lazy #19420

Closed
artuska opened this issue Sep 26, 2017 · 21 comments
Closed

Router's ActivatedRoute data returns empty {} if module is lazy #19420

artuska opened this issue Sep 26, 2017 · 21 comments
Labels
area: router needs reproduction This issue needs a reproduction in order for the team to investigate further
Milestone

Comments

@artuska
Copy link

artuska commented Sep 26, 2017

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.

@artuska artuska changed the title Router data returns empty {} for lazy modules Router's ActivatedRoute data returns empty {} if module is lazy Sep 27, 2017
@vicb vicb added area: router needs reproduction This issue needs a reproduction in order for the team to investigate further labels Sep 28, 2017
@artuska
Copy link
Author

artuska commented Sep 29, 2017

Tried to subscribe for route changes in my app.component.ts:

import 'rxjs/add/operator/filter';

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)
            .subscribe(e => this.handleNavigationEnd());
    }

    handleNavigationEnd() {
        console.log('routerState:snapshot', this.router.routerState.snapshot.root.data); // always {}

        this.route.data.subscribe((data) => console.log('ActivatedRoute:subscribe', data)); // always {}

        this.scrollTop();
    }

    scrollTop() {
        window.scrollTo(0, 0);
    }

}

@artuska
Copy link
Author

artuska commented Sep 29, 2017

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

@Dok11
Copy link
Contributor

Dok11 commented Jan 22, 2018

Thanks, you just saved me

@ngbot ngbot bot added this to the needsTriage milestone Feb 26, 2018
@loicmarie
Copy link

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

@non4me
Copy link

non4me commented May 1, 2018

Try it:

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

@Dok11
Copy link
Contributor

Dok11 commented May 2, 2018

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

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

@jasonaden
Copy link
Contributor

@artuska Can you produce a reproduction? I would like to look into this. Please add a StackBlitz with a minimal repro.

@jasonaden
Copy link
Contributor

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.

@pathurs
Copy link

pathurs commented Jun 10, 2018

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.

@yozman
Copy link

yozman commented Aug 17, 2018

@jasonaden
same problem, any update?

@yozman
Copy link

yozman commented Aug 29, 2018

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

hachi-eiji added a commit to hachi-eiji/sandbox-todo-app that referenced this issue Sep 2, 2018
routerに紐付いたほうが良さそうだ

angular/angular#19420
@romatron
Copy link

romatron commented Sep 28, 2018

one more solution

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

@LeoDupont
Copy link

@romatron Thank you so much!
I've been in trouble for so long because of this...

@lufonius
Copy link

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

@dsozzi
Copy link

dsozzi commented Dec 3, 2018

Is it normal that I get the data in the format of:

{0: "P", 1: "a", 2: "g", 3: "e", 4: ":", 5: "1", 6: "5", 7: "0", 8: "4", 9: "8", 10: "3", 11: "7", 12: "5", 13: "5", 14: "1", 15: "6", 16: "6", 17: "7"}

@jakubszalaty
Copy link

For Angular 7.2.4

constructor(private route: ActivatedRoute) {}
ngOnInit() {
   const currentActivatedRoute = this.route.pathFromRoot[this.route.pathFromRoot.length - 1]
   currentActivatedRoute.data.subscribe((v) => console.log(v))
}

@CAspeling
Copy link

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.

@nikita-fuchs
Copy link

@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 ?

@BruneXX
Copy link

BruneXX commented Aug 28, 2019

Hi Guys, I've followed this: https://stackblitz.com/github/jbojcic1/angular-routing-example/tree/routing-reuse-layout-example-3-with-subscribing-to-route-events?file=src%2Fapp%2Fapp-routing.module.ts

But I can get that working with angular 8.2.0 any ideas what could be happening here?

@BruneXX
Copy link

BruneXX commented Aug 28, 2019

Please if anyone is having problem with this, please check this: #31778 (comment)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: router needs reproduction This issue needs a reproduction in order for the team to investigate further
Projects
None yet
Development

No branches or pull requests