angular: ActivatedRoute doesn't carry data

UPD: Given discussion below, apparently the behavior is what it supposed to be. I am not closing, as there might be some more discussion.

I’m submitting a … [x ] bug report

Current behavior I created a couple of routes with data which I want to use when a router link is clicked:

const routes: Routes = 
[
    {path: 'run', data: {title: "Runner"}, component: RunComponent},
    {path: 'walk', data: {title: "Walker"}, component: WalkComponent}
];

Then in my app I subscribed for ActivatedRoute data to extract the data defined on each route:

  constructor(private route: ActivatedRoute) {
  }
 public ngOnInit() {
     this.routeData= this.route.data.subscribe((data) => {
       this.name = data.title;
     });
  }

When I click on the link the data object is empty

Expected behavior I expect the ActivatedRoute to provide data that is defined on the currently active link

Reproduction of the problem See Plunker http://plnkr.co/edit/I62gnvThO0nGN2jk9xhX?p=preview

  • Angular version: 2.0.0
  • Router version: 3.0.0

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 16
  • Comments: 33 (4 by maintainers)

Most upvoted comments

Closing this issue, as it works as expected.

It doesn’t work @vsavkin , why is it closed. this.activatedRoute.data.subscribe(res => console.log(res)); It’s logging blank object while I’m passing in data prop in my routes configuration.

You could listen on the router state events.

  constructor(private route: Router) {
  }

  public ngOnInit() {
     this.routeData= this.route.events.subscribe((data) => {
       if (data instanceof RoutesRecognized) {
        this.name = data.state.root.firstChild.data.title;
       }
     });
  }

I am facing the same issue as @paramsingh88 but in difference to the plunker from @lroitman I want to subscribe to this.activatedRoute.data not in the component which is registered in the routes config but in a shared component. This component is included in every page by adding the component selector tag in my layout html. For some reason the observable never get a next result. When I listen to the router events of type NavigationEnd I can get the routes data… but sadly this is not working (at least for me) when the webapp loads for the first time.

This is the router event based approach:

this.router.events
            .filter((event) => event instanceof NavigationEnd)
            .map(() => this.route)
            .map((route) => {
                while (route.firstChild) route = route.firstChild;
                return route;
            })
            .filter((route) => route.outlet === 'primary')
            .mergeMap((route) => route.data)
            .subscribe((event) => {
                this.updateTitle(event["title"])
            });

And this is what is currently not working for me:

@Component({
    moduleId: module.id,
    selector: 'navbar-cmp',
    templateUrl: 'navbar.component.html'
})
export class NavbarComponent implements OnInit {
    ...
    constructor(private route: ActivatedRoute){}
    ...
    ngOnInit(){
        this.route.data.subscribe(data => {
            this.updateTitle(data['title'])
        });
    }
}

As well as console.log(this.route.snapshot.data); prints an empty object for me.

This is the corresponding part from my route config (notice that it will instantiate the UserEditComponent and not the NavbarComponent):

           {
                path: 'users/:id/edit',
                component: UserEditComponent,
                data: {
                    title: "Edit user"
                }
            }

Occurred with angular 4.3.3.

@rainerpl your shared component isn’t going to get the current activated route because its not in the route tree. Every component used in routing gets its own ActivatedRoute and the specific data that goes along with it. Injecting ActivatedRoute into a common service or your root application template is going to get the root ActivatedRoute, which doesn’t contain any component specific data.

You inject the ActivatedRoute in a shared component, you use the same approach used @danielclasen with the router.events, traversing down the route children and get the route data from the route at the bottom of the tree.

Here is an example plunker: http://plnkr.co/edit/6iVjZcnqKBi7Dpp3KrFX?p=preview

Same problem here.

I was trying to make a generic <header> component that is shown on all pages. the header is supposed to grab a title attribute from route.data, but, it is always {}

Im having issues with this in Angular 5 as well.

I do not find it logical that Angular ‘makes me search through all nested firstChild.data objects’ and retrieve the data {}…

As a developer I want to access the activated state object from my AppComponent with the data property as well.

My case, lets say I have these routes:

const routes: Routes = [
  {
    path: 'rides',
    children: [
      {
        path: '',
        component: RidesComponent
      },
      {
        path: 'ride/:id',
        component: RideComponent
      },
      {
        path: 'generate',
        component: GenerateComponent,
        data: {
          layoutCenter: true
        }
      }
    ]
  }
]; 

In my AppComponent i have

  ngOnInit() {

    this.router.events.subscribe((evt) => {
      if (evt instanceof RoutesRecognized) {

        // this is always undefined! ... it should hold my data
        console.log(evt.data);

        // why can i only find my data when i go to deeply nested param?
        console.log(evt.state.root.firstChild.firstChild.data);
      }
  }

So as you can see, in this examples I have 2 levels before reaching my state… I have to go really deep in the objects and search for a data param. It should just be available in the top, in the current state object. Please implement this Angular!

Thanks, deeply appreciated.

[EDIT] I solved it using my recursive search data param… https://paste2.org/N0cjkBjA

@vsavkin Also run into this surprising problem. Think the current design violiates “the principle of least surprise”. Should be changed.

Route components are reused by default, so clicking on a link to the same route multiple times isn’t going to destroy and re-create the component.

        path: 'run',
        data: {title: "Runner"},
        component: RunComponent

Data will be available in activated route injected into component RunComponent.

App component does not have route and does not have data mapped to it.

Here is how i do it

    ngOnInit(): void {
        this.routerSub$ = 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)
            .subscribe((data) => {
                this.title = data.title || 'My website';
            });
    }

    ngOnDestroy(): void {
        this.routerSub$.unsubscribe();
    }

This solution relies on some additional rxjs operators

`

Ohh i want get my url segments but can’t

+1 Observe such issue on angular 4.4.1 data always providing empty object

@brandonroberts You are correct, but the ActivatedRoute, according to documentation, should provide information about the current route, and current route has data I want to use elsewhere.

Note that in one of the beta versions the data was available. See this plunk: http://plnkr.co/edit/3iOaYA?p=preview (this was forked out of plunk for router 3.0.0-beta.2, angular 2.0.0-rc.4

I too ran into this problem today. While I kind of understand why the current solution is implemented the way it is - it’s still a bit confusing at first. Also took a while to find this issue and find a solution (going with the router.events strategy, which indeed works fine). Maybe there should be a comment about this in the documentation?

As is, the documentation of ActivatedRoute does make this comment:

Contains the information about a route associated with a component loaded in an outlet.

Which gave me the clue to start looking for this.

While the problem is obvious, the solution is hard to find without finding this thread.

got the same, confirmed on Angular 4.3.4

@lroitman That’s because you’re retrieving the ActivatedRoute data inside the component while others are trying to retrieve it outside the component (if I understand correctly).

For example, I have this in my app.module.ts:

const routes: Routes = [
  {
    path: '',
    component: AssetsViewComponent,
    canActivate: [AuthGuard]
  },
...
];

export class AppModule {
   private properties: Properties;
   private subscription: Subscription;

   constructor(private router: Router){

    // workaround to retrieve data from the router to avoid retrieving from the ActivatedRoute in each component
    this.subscription = this.router.events
      .pipe(skipWhile(data => !data))
      .subscribe((data: any) => {
        if (data instanceof RoutesRecognized) {
          this.properties = data.state.root.firstChild.data.properties;
          console.log(this.properties);

          this.subscription.unsubscribe();
        }
      });
  }

and this in the component that is registered in my AssetsViewComponent, which is specified as the component in my route:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
    this.route.data.subscribe(data => {
      console.log(data);
    });
}

Both strategies achieve the same thing, which is retrieving the data that I pass along with the route. I believe the struggle here is that you can’t just refer to the ActivatedRoute straight from the app.module and would thus be forced to duplicate the latter code in every component a route points to, compared to just having a single statement defined in the app.module.

Could someone from the Angular team explain why a top-level route is considered a child route in the ActivatedRoute tree?

With the below AppModule…

@NgModule({
    imports: [
        ...
        RouterModule.forRoot([
            {path: 'run', data: {title: "Runner"}, component: RunComponent},
            {path: 'walk', data: {title: "Walker"}, component: WalkComponent}
        ])
        ...
    ]
})
export class AppModule { }

…when I subscribe to Router.events and then attempt to access ActivatedRoute after navigating, for example, to /run

export class AppComponent implements OnInit {
    constructor(private router: Router, private route: ActivatedRoute) { }
    
    ngOnInit() {
        this.router.events
            .filter(e => e instanceof NavigationEnd)
            .subscribe(event => {
                console.log(this.route.data) // I expect the output to be the data property of the current route
                console.log(this.route.firstChild.data) // Here I get `{ title: "Runner" }`
            })
    }
}

…I expect to get that data on this.route, but instead I get it on this.route.firstChild. Am I missing something?