53
const appRoutes: Routes = [
  { path: 'parent', component: parentComp, data: { foo: 'parent data' }, children: [
    { path: 'child1', component: childComp1, data: { bar: 'child data 1' },
    { path: 'child2', component: childComp2, data: { bar: 'child data 2' }
  ]}
];

If I navigate to /parent/child2 and then look at the ActivatedRoute from parentComp, data.foo is defined, but data.bar is not. I have access to an array of all the children, but I don't know which one is activated.

How can I access the activated child route's data from a parent route's component?

adamdport
  • 11,687
  • 14
  • 69
  • 91

6 Answers6

73

First child will give you access to data

constructor(route: ActivatedRoute) {
  route.url.subscribe(() => {
    console.log(route.snapshot.firstChild.data);
   });
}
Julia Passynkova
  • 17,256
  • 6
  • 33
  • 32
  • 7
    so `firstChild` is not the first child defined in the route children, but the first child of its activated children? – adamdport May 05 '17 at 14:41
  • 2
    do you know how to use observable in the same scenario? I tried this.route.firstChild.data.subscribe but it's not being triggered when I navigate to other child component? – jbojcic Jul 06 '17 at 15:38
  • 3
    @Julia, why don't you nest those child-routes underneath a componentless route/namespace? Your answer changes from `route.firstChild.data` to `route.firstChild.firstChild.data`. Nest it again and you've `route.firstChild.firstChild.firstChild.data`. That could mean a heavy ChangeCost if you have multiple references. What `ActivatedRouteSnapshot` needs is a `route.lastChild` to always get the object furthest down the chain. The question is, *Is `route.firstChild` the first declared in my route's `children` array? Or, is is the next in, say, `"/root/grandparent/parent/child/grandchild"`?* – Cody Jul 12 '17 at 00:02
  • 5
    @Cody When you introduce children, **you can have multiple activated routes.** The parent, the child, and the grandchild are all activated. Each parent can access it's activated child with route.firstChild. If you need the deepest activated child, you can do something like this `while (route.firstChild) { route = route.firstChild }` – adamdport Jul 12 '17 at 14:43
  • 1
    What if I have multiple (sibling) child routes? How to get the data from a specific one? – Inigo Jul 16 '17 at 18:21
  • @Inigo it's not trivial to access data from a non-active route. You can see your router config using `router.config`, but it doesn't get updated with lazily loaded routes. If you need to access that data from another route, you probably shouldn't define it in your route definitions. Consider defining a map or something in a constants file somewhere. – adamdport Jul 17 '17 at 15:54
  • For me `route.snapshot.firstChild` is null. As @adamdport mentioned this is because it is lazy loaded. However it is 'trivial' to get this data if you listen for `router.events` and filter for the `NavigationEnd` event at which point `route.snapshot.firstChild.data` will be the data. – Simon_Weaver Jun 11 '18 at 19:20
  • 1
    @Julia you forgot to put "private" it's too convenient to omit. and using this only in the constructor is next to useless, if you write "private route: ActivatedRoute" then you can externalize that whole code by adding "this." in front of every "route" . – tatsu Jun 13 '18 at 08:55
30

Working with Angular 6, I managed to get the current route data from a parent component, with the following code:

I've configured the Router with the Extra options to inherit the parent routes data:

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      initialNavigation: 'enabled',
      paramsInheritanceStrategy: 'always'
    }),
  ...
})
export class AppModule {}

Then in my parent component, I was able to see the data changes with:

import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';

subs: Array<Subscription> = [];

constructor(private router: Router, private route: ActivatedRoute) {
  this.subs[0] = this.router.events
    .pipe(
      filter(event => event instanceof NavigationEnd),
      map(() => this.route.snapshot),
      map(route => {
        while (route.firstChild) {
          route = route.firstChild;
        }
        return route;
      })
    )
    .subscribe((route: ActivatedRouteSnapshot) => {
      console.log(route.data);
    });
}

ngOnDestroy() {
  this.subs.forEach(s => s.unsubscribe());
}

Enjoy!

Mateo Tibaquira
  • 2,059
  • 22
  • 23
  • this is pretty good but why do I get ever increasing amounts of returns? 0 logs on app load, 1 on first navigation, 2 logs on second, 3, 4, 5 ... ect. I only want one call. – tatsu Jun 13 '18 at 09:06
  • 1
    (since I'm not doing this inside a constructor my subscribe was being called multiple times without an unsubscribe) Okay so here's what I did : ```let subscription = this.router.events .pipe( filter(event => event instanceof NavigationEnd), map(() => this.route.snapshot), map(route => { console.log(route.firstChild.data); subscription.unsubscribe(); }) ).subscribe(); ``` – tatsu Jun 13 '18 at 09:32
  • good point! I just added the subscription control I have on my components ;) I use this on my layout component, so I receive configuration data from the routing, I have to listen it until it's destroyed – Mateo Tibaquira Jun 13 '18 at 15:57
  • 1
    I like that this answer combines all of the previous answers and comments into one final answer. – Luminous Oct 10 '19 at 16:04
  • 1
    Working with angular 8! Even without `initialNavigation: 'enabled', paramsInheritanceStrategy: 'always'` parameters! – Igor Kurkov Jan 16 '20 at 09:57
  • Thank you for the answer. It's frustrating that such a simple thing in Angular is achieved with so many lines of code... – Alexey Grinko Jun 04 '20 at 17:35
  • @AlexeyGrinko which Angular version are you using? I have to test if this is the default behavior in Angular 8/9, anyways, I realized that the Angular Router is so powerful that this kind of granular control is good to have, but indeed, the defaults should work for the majority, who expects inhenitance :) – Mateo Tibaquira Jun 07 '20 at 18:55
  • @MateoTibaquira I'm using 9 – Alexey Grinko Jun 08 '20 at 09:05
8

In Angular 8 work with:

data: any;

constructor(route: ActivatedRoute) {
  this.data = route.snapshot.firstChild.data;
}
configbug
  • 746
  • 12
  • 19
  • Thank you, good to know there's no need of .subscribe anymore! – Gustavo Barros May 11 '20 at 18:30
  • 3
    @GustavoBarros how come there's no need of .subscribe? This way you will have only data for the first activated route, and it won't change when you change the route inside of the current component. Either you should subscribe to the changes, or use smth like `get data() { return this.route.snapshot.firstChild.data; }` instead of `data: any;` to always get data of the currently active sub-route – Alexey Grinko Jun 04 '20 at 17:40
3
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';

@Component({
  selector: 'sources',
  templateUrl: './sources.component.html',
  styleUrls: ['./sources.component.scss'],
})
export class SourcesComponent implements OnInit, OnDestroy {

  constructor(private route: ActivatedRoute, private router: Router) { }

  private sub: Subscription;
  public title: string;

  ngOnInit() {
    this.sub = this.router.events.pipe(
      filter(event=> event instanceof NavigationEnd)
    )
    .subscribe(events=>{
      console.log(this.route.snapshot.firstChild.data);
    })
  }

  ngOnDestroy(){
    this.sub.unsubscribe();
  }

}

My router looks like:

const routes: Routes = [
  {
    path: '',
    component: SourcesComponent,
    children: [
      {
        path: 'source',
        component: SourcesTableComponent,
        data: {title : 'Источники'},
      },
      {
        path: 'category',
        component: CategoryComponent,
        data: {title : 'Категории'}
      },
      {
        path: 'relation',
        component: RelationComponent,
        data: {title : 'Сведение категорий'}
      },
    ]
  },
];
DaddyPug
  • 31
  • 1
3

I am using Angular 8

Here is the routes

      {
    path: '',
    component: DefaultLayoutComponent,
    canActivate: [AuthGuardService],
    children: [
      {
        path: 'dashboard',
        component: DashboardComponent,
        data: { title: 'Summary' },
      },
      {
        path: 'account',
        component: AccountComponent,
        data: { title: 'account' },

      },
      {
        path: 'setup',
        component: setupComponent,
        data: { title: 'setup' },

      },
      {
        path: 'help',
        component: helpComponent,
        data: { title: 'help' },

      },
    ],
  }

Parent Component - DefaultLayoutComponent


constructor(private router: Router,    private route: ActivatedRoute) { } 

ngOnInit(): void {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        const title = this.route.snapshot.firstChild.data;
        console.log('title-', title);

      }
    });
}

Output from console Access to the data in my parent component

Dharman
  • 30,962
  • 25
  • 85
  • 135
Vijay
  • 31
  • 2
1

Still spend some time to get a correct answer. ActivationEnd event what helped me

Router example:

const routes: Routes = [
{
    path: '',
    component: CustomComponent, // HTML parent wrapper with menu 
    data: {},
    children: [
        {
            path: '',
            component: SearchComponent,
            data: { someId: 'id-1' }
        },
        {
            path: 'somePath/create',
            pathMatch: 'full',
            component: CreateComponent,
            data: { someId: 'id-2' }
        },

Under your parent CustomComponent

    constructor( private router: Router ) {

       this.router.events
        .pipe(filter((routerEvent) => routerEvent instanceof ActivationEnd))
        .subscribe((routerEvent: ActivationEnd | any) => {
            const data = routerEvent.snapshot.data;
            if (data?.someId) {
                console.log('someId', data.someId);
            }
        });
     }
Dmitri Algazin
  • 3,332
  • 27
  • 30