Search code examples
angularroutesangular-routing

Separate routing logic from component logic in Angular


I'm trying to learn Angular, and I have a separation-of-concerns problem with routing. In all guides I found, when happens is that you specify the route and parameters in the routing module, but parse them in the component file. For example, in the classic Tour of Heroes example:

// in app-routing.module.ts
{ path: 'hero/:id': component: HeroDetailComponent }

// in hero-detail.component.ts
constructor(
  private route: ActivatedRoute,
  private heroService: HeroService,
  private location: Location
) {}

ngOnInit() {
     const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
    .subscribe(hero => this.hero = hero);
}

But earlier in the tutorial, what you had was that a Hero object was used as Input():

@Input() hero: Hero;

And you would bind to it:

<hero-detail [hero]='hero'></hero-detail>

What I would want is to be able to keep that logic, and instead parse the URL and pass the Input()s in the same file as the routing module, and keep the component file as-is. Something like:

// app-routing.module.ts
{
    path: 'hero/:id', component: HeroDetailComponent, inputs: path => ({ hero: getHeroObject(+path.params["id"]) })
}

Is there any option in Angular routing to do something like this? If not, can you think of a roundabout-way to separate the logic parts? One thing of which I thought is to create separate components in the routing module for each path, each of them renders the "pure-logic" component, something like:

// app-routing/hero-detail-route.component.ts
constructor(
  private route: ActivatedRoute,
  private heroService: HeroService,
  private location: Location
) {}

ngOnInit() {
     const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
    .subscribe(hero => this.hero = hero);
}

<!--- app-routing/hero-detail-route.component.html -->
<hero-detail [hero]='hero'></hero-detail>

But that's just kind of "kicking the problem down the road", isn't it?

Any ideas?

Thanks in advance!


Solution

  • You can do it in 2 ways.

    1. Using resolvers

    You can attach a resolver in the route like

    {
        path: 'hero/:id',
        component: HeroDetailComponent,
        resolve: {
            hero: HeroDetailResolver
        }
    }
    

    HeroDetailResolver will contain all the logic to get the hero detail from server. In component you catch resolver data.

    this.route.data.subscribe(data => {
        this.hero = data.hero;
    });
    

    If you are not familiar with angular resolvers they are a handy tool to pre-fetch some server data before component loads.

    2. Using smart parent and dumb child components

    Use a smart parent component to fetch all server data and provide them to dumb child components. In this case you can make HeroDetailLayoutComponent to fetch the hero detail. Then pass it as @Input() to HeroDetailComponent.

    // in app-routing.module.ts
    { path: 'hero/:id': component: HeroDetailLayoutComponent }
    
    // in hero-detail-layout.component.ts
    constructor(private route: ActivatedRoute, private heroService: HeroService) {}
    
    ngOnInit() {
        const id = +this.route.snapshot.paramMap.get('id');
        this.heroService.getHero(id).subscribe(hero => this.hero = hero);
    }
    
    // hero-detail-layout.component.html
    <hero-detail [hero]='hero'></hero-detail>
    
    // in hero-detail.component.ts
    @Input() hero: any[];