1

Is there a best practice approach in Angular2+ for making the URL the single source of truth for application state.

EmberJs has Route Models, each segment of the URL can have a model and each URL segment model is resolved from left to right, so when resolving the model for a nested page, you can use the model from parent pages because the nested page model isn't resolved until all parent models have finished.

I'd like to achieve something similar in Angular2 if it is a common or recommended approach.

I've added NGRX and NGRX Router Store to my project. Router Store will dispatch an action when the URL changes, though it seems like I have to duplicate my routing logic to use it; The action payload includes the URL so in @Effects that deal with ROUTER_NAVIGATION actions I have to pattern match on the URL to decide what has to happen. Is this true?

Let's assume I have a URL structure like:

  • /list-of-things/
  • /list-of-things/thing/
  • /list-of-things/thing/thingy-1/
  • /list-of-things/thing/thingy-2/

All of these routes need the data from list-of-things. /thing/ needs the data for /list-of-things/ to be loaded first.

/thingy-1/ and /thingy-2/ both need the data loaded in /list-of-things/ and /thing/.

I could use a service to get close to what I want. The service would be responsible for fetching the data that list-of-things and thing need. Then, in Effects that match URLs for nested routes, I can use the same service and the data wouldn't get requested again if it's already loaded. If a user arrives at the app via a deep link and therefore the data that would have been loaded when visiting list-of-things isn't in the Store, the same service would be used to request the data.

Using a service when I'm using NGRX and Effects seems like it goes against the point of effects? Is there a better approach?

2

1 Answer 1

0

This is possible using Angular Route Resolvers. The setup is more involved than in Ember, but it works as requested. Example taken from:

https://angular.io/api/router/Resolve

First, create a route resolver:

@Injectable({ providedIn: 'root' })
export class HeroResolver implements Resolve<Hero> {
  constructor(private service: HeroService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any>|Promise<any>|any {
    return this.service.getHero(route.paramMap.get('id'));
  }
}

Add the resolver to your route:

RouterModule.forRoot([
    {
        path: 'detail/:id',
        component: HeroDetailComponent,
        resolve: {
          hero: HeroResolver
        }
    }
])],

Get the data from the ActivatedRoute in your component:

import { Component, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css'],
})
export class HeroDetailComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  hero: any

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

To adapt this to use along side ngrx and effects, the route resolver could dispatch actions and resolve a promise when store data is present.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.