18

Given the following route:

path: '',
component: MyComponent,
resolve: {
    foo: FooResolver,
    bar: BarResolver
}

Is there any way of telling angular to execute the first resolver FooResolver, and only execute the second resolver BarResolver once the first one has finished?

Ivan Aguilar
  • 565
  • 1
  • 7
  • 22
  • Whatever you're trying to do. There is a better way. – Carsten Oct 17 '17 at 14:32
  • It depends on what `foo` and `bar` are and why they should be resolved in this order. But generally this is achievable with FooBarResolver that resolves to an object that contains foo and bar. – Estus Flask Oct 17 '17 at 14:39
  • @estus I was thinking about doing that, and if there's no other way I will, but I'd prefer to do it with different resolvers for reutilization purposes. – Ivan Aguilar Oct 17 '17 at 14:41
  • @estus basically `foo` and `bar` are json objects retrieved from a database, I don't care of the order of execution, but they must be executed one after the other. – Ivan Aguilar Oct 17 '17 at 14:43
  • 1
    I had same problem but more complex. In my case `bar` and `baz` depended on `foo` but `baz` was optional in some routes. Eventually I ended up with `FooBarBaz` resolver that took care of optional Baz. To my knowledge, there is currently no better way. – Estus Flask Oct 17 '17 at 15:01

4 Answers4

34

Resolvers are resolved in parallel. If Foo and Bar are supposed to be resolved in series they should be a single FooBar resolver. If they are supposed to be used by themselves in other routes, FooBar can wrap Foo and Bar resolvers:

class FooBarResolver implements Resolve<{ foo: any, bar: any }> {
  constructor(
    protected fooResolver: FooResolver,
    protected barResolver: BarResolver
  ) {}

  async resolve(route): Promise<{ foo: any, bar: any }> {
    const foo = await this.fooResolver.resolve(route);
    const bar = await this.barResolver.resolve(route);

    return { foo, bar };
  }
}

FooBar should be aware of the fact if it is a promise or an observable that is returned from Foo and Bar in order to resolve them properly. Otherwise additional safety device should be added, like await Observable.from(this.fooResolver.resolve(route)).toPromise().

FooBar and Foo or Bar shouldn't appear within same route because this will result in duplicate resolutions.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 2
    @DanyGauthier As with any other resolver, it's specified as `resolve: { fooBar: FooBarResolver }` on the route and resolved data is available as `activatedRoute.data['fooBar'].foo`, etc in route component. – Estus Flask Aug 29 '18 at 15:51
1

I found a slightly more elegant solution that can be used if you don't care about the results from all of the resolvers:

class FooBarResolver implements Resolve<Observable<any>> {
    constructor(
        protected fooResolver: FooResolver,
        protected barResolver: BarResolver
    ) { }

    resolve(): Observable<any>
    {
        return this.fooResolver.resolve().pipe(
            concat(this.barResolver.resolve()),
            concat(this.barResolver.resolve())
        );
    }
}

I use this to trigger the data loading in my services. And because they write the data / isLoading / error into an Akita storage, I don't care about the results of the resolvers.

C. Fritz
  • 19
  • 1
  • 3
1

One other option is to wrap your dependent routes and use necessary resolver on wrapper.

I.E.

{ path: '', resolve: { foo$: FooResolver }, children: [
    { path: 'mySubRoute', resolve:  {bar$: BarResolver }, component: BarComponent }
]}

*If you meet with relative path resolving problems, try using

relativeLinkResolution: "corrected"

In forRoot router method. More info here Angular RouterLink relative path does not work in component having empty path

simply good
  • 991
  • 1
  • 12
  • 26
0

I tackled this scenario using combineLatest in a single resolver. You can do this:

@Injectable({providedIn: 'root'})
export class FooBarResolver implements Resolve<any> {
  constructor(
    private readonly fooResolver: FooResolver,
    private readonly barResolver: BarResolver  
  ) {}

  resolve() {
    return combineLatest(
      this.fooResolver.resolve(), 
      this.barResolver.resolve()
    ).pipe(map(([users, posts]) => ({users, posts})))
  }
}
smtaha512
  • 420
  • 5
  • 11