5

I'm tying to get the child route data('layout') from parent component using ActivatedRoute. I have tried the following example and I'm able to get the data I'm looking for but only for one time. During child route changes, I don't see the updated 'layout' value.

Routes

const routes: Routes = [
  {
    path: '',
    component: Layout1Component,
    children: [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        loadChildren: './pages/analytics/analytics.module#AnalyticsModule',
        data: {
          layout: 'boxed'
        }
      },
      {
        path: 'files',
        loadChildren: './pages/files/files.module#FilesModule',
        data: {
          layout: 'full'
        }
      }
    ]
  }
];

Layout1Component

export class Layout1Component implements OnInit {
  constructor(private service: AppService, route: ActivatedRoute) {
    this.layout = route.snapshot.firstChild.data['layout'];

    route.url.subscribe(() => {
      console.log(route.snapshot.firstChild.data['layout']);
    });
  }
}

I want the console.log to be printed with updated 'layout' value every time the route is changed.

Please help me fix this.

Body
  • 3,608
  • 8
  • 42
  • 50

3 Answers3

10

From my understanding, the reason why you're getting the data only once is because ActivatedRoute gets the url as a BehaviorSubject when it is instantiated.

function createActivatedRoute(c: ActivatedRouteSnapshot) {
  return new ActivatedRoute(
      new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
      new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
}

Here's the link for the above snippet

This means that when you subscribe, you'll only get that first stored value.

One way to solve it is to make use of the router events.

Assuming you have something like this:

const routes: Route[] = [
  { 
    path: '', 
    component: HelloComponent,
    children: [
      {
        path: 'foo',
        loadChildren: () => import('path/to/foo').then(m => m.FooModule),
        data: {
          name: 'andrei'
        }
      },
      {
        path: 'bar',
        loadChildren: () => import('path/to/bar').then(m => m.BarModule),
        data: {
          name: 'john'
        }
      },
    ]
  },
]

Here is a possible solution:

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

 ngOnInit () {
    const routeEndEvent$ = this.router.events
      .pipe(
        filter(e => e instanceof NavigationEnd),
        tap(() => console.warn("END")),
    );

    this.router.events
      .pipe(
        filter(e => e instanceof ChildActivationEnd && e.snapshot.component === this.route.component),
        buffer(routeEndEvent$),
        map(([ev]) => (ev as ChildActivationEnd).snapshot.firstChild.data),
        takeUntil(this.ngUnsubscribe$)
      )
      .subscribe(childRoute => {
        console.log('childRoute', childRoute);
      })
  }
  • routeEndEvent$ is an observable that will emit only when the route is ready(NavigationEnd)

  • filter(e => e instanceof ChildActivationEnd && e.snapshot.component === this.route.component): after examining a while the events emitted by the router, I reached this solution to determine whether the component(HelloComponent) is the parent(this.route.component) of the route which changed(e.snapshot.component).

  • buffer(routeEndEvent$): we only want to act when the router reached its final state(NavigationEnd). here you can read more about the buffer operator

StackBlitz

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • 1
    Was experiencing a similar issue where the url would change but the activated route wouldn't react. Forcing a reload of the page based on router event subscriptions helped unblock me – TabsNotSpaces Jan 15 '21 at 01:16
  • This looked like what I wanted, but didn't work. first run when the childComponent is activated (using routerLink="child" in html) the parent component updates the childRoute, but not when the route is changed to "/parent" (using routerLink="" in html). (my mistake.. I should use routerLink=".." to navigate up. ) =D – JimiSweden Oct 19 '21 at 21:31
  • using routerLink=".." still doesn't do it for me. :/ – JimiSweden Oct 19 '21 at 21:38
  • Hi @JimiSweden, could you create a StackBlitz app with the problem? – Andrei Gătej Oct 20 '21 at 08:53
0

I had the same issue, which was fixed when I subscribed to the "data" property of ActivatedRoute. Below is an example.

Component:

constructor (private route: ActivatedRoute)  {
        this.route.data.subscribe(({ listData }) => {
        this.listData = listData
    })
}

Route:

{
    path: ':id',
    component: Component,
    resolve: {
        listData: ModuleService
    }
},
K. Waite
  • 1,574
  • 13
  • 12
0

I solved the problem using the firstChild attribute of the ActivatedRoute.

this._route.firstChild?.paramMap
  .subscribe((paramMap) => {
    console.log('Params: ', paramMap);
  });

It returns another ActivatedRoute object reference to the child route, which is the route that actually have the param.