angularangular-routingangular8angular-activatedroute

ActivatedRoute does not update the child route data on route changes


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.


Solution

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

    StackBlitz