Using Resolve In Angular2 Routes
Asked Answered
A

5

32

In Angular 1 my config looks like this:

$routeProvider
  .when("/news", {
    templateUrl: "newsView.html",
    controller: "newsController",
    resolve: {
        message: function(messageService){
            return messageService.getMessage();
    }
  }
})

How to use resolve in Angular2?

Anesthetize answered 1/11, 2015 at 13:15 Comment(2)
AFAIK this functionality isn't implemented yet. Watch for this issue. For now you can try to use @CanActivate and @OnActivate hooks (but it's not resolve functionality of course).Borlow
Possible duplicate of Angular2 : get parent router dataMccandless
N
4

@AndréWerlang's answer was good, but if you want the resolved data on the page to change when the route parameter changes, you need to:

Resolver:

@Injectable()
export class MessageResolver implements Resolve<Message> {

  constructor(private messageService: MessageService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Message> {
    const id = +route.params['id'];
    return this.messageService.getById(id);
  }
}

Your component:

ngOnInit() {
  this.route.data.subscribe((data: { message: Message }) => {
    this.message = data.message;
  });
}
Nonlinearity answered 6/3, 2017 at 2:12 Comment(0)
P
28

As alexpods has already mentioned, there doesn't seem to be a 'resolve' as such. The idea seems to be that you make use of the lifecycle hooks that the router provides. These are:

  • canReuse
  • canDeactivate
  • onActivate
  • onReuse
  • onDeactivate

Then there is @CanActivate. This is a special hook because it is called before your component is instantiated. Its parameters are (next, previous) which are the components you're routing to and the component you've come from (or null if you have no history) respectively.

import {Component} from '@angular/core';
import {ROUTER_DIRECTIVES, CanActivate, OnActivate} from '@angular/router';

@Component({
    selector: 'news',
    templateUrl: 'newsView.html',
    directives: [ROUTER_DIRECTIVES]
})

@CanActivate((next) => {
    return messageService.getMessage()
        .then((message) => {
            next.params.message = message;
            return true; //truthy lets route continue, false stops routing
        });
})

export class Accounts implements OnActivate {

    accounts:Object;

    onActivate(next) {
        this.message = next.params.message;
    }
}

The thing I have not figured out yet is how to get the result of the promise into your onActivate - other than storing it on your 'next' component. This is because onActivate is also only invoked with next and previous and not the result of the promise. I'm not happy with that solution but it's the best I could come up with.

Picture answered 8/12, 2015 at 15:41 Comment(7)
just FYI, the hook is routerOnActivate (at least in ES6/7)Plymouth
the comment above means the OnActivate interface is implemented via routerOnActivate, not that OnActivate can be implemented to delay template load.Gowrie
How are you injecting your messageService? I'm attempting to use Injector.resolveAndCreate(). It apparently cannot provide application-wide dependencies specified in bootstrap() to the services it is creating. The result is "No provider for Http", for example, or other things my service requires.Gowrie
I'm also interested, how do you do dependency injection of services into @CanActivate?Headstall
@Gowrie I've found the solution to DI in decorators like @CanActivate. Save a reference to the appInjector using the promise from bootstrap and use that to inject. See this plunker: plnkr.co/edit/SF8gsYN1SvmUbkosHjqQ?p=previewHeadstall
How this could be done in RC? Is there a better a way now after the RC out?Synergistic
2017 here -- see the updated answer with example below (or above) by @andré-werlangDenary
S
28

Based on @angular/router v3-beta, these are the required steps.

Implement a resolver that returns an Observable or a plain value:

@Injectable()
export class HeroResolver implements Resolve {

    constructor(
        private service: HeroService
    ) {}

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Hero> {
        const id = +route.params['id'];
        return Observable.fromPromise(this.service.getHero(id));
    }

}

Note that in case you return an observable, the unwrapped value (first one) will be available through route.snapshot.data. If you want the observable itself to be available, then you need to wrap it in another Observable:

return Observable.of(source$);

Add the resolver to your route:

export const HeroesRoutes: RouterConfig = [
    { path: 'heroes',  component: HeroListComponent, resolve: { heroes: HeroesResolver } },
    { path: 'hero/:id', component: HeroDetailComponent, resolve: { hero: HeroResolver } }
];

Finally, provide your resolve class and any dependency to bootstrap or your main component providers:

bootstrap(AppComponent, [
    HeroesResolver, HeroService
])

Consume the resolved data from an ActivatedRoute instance:

ngOnInit() {
    this.hero = this.route.snapshot.data['hero'];
}

Remember that snapshot means the value at the state of execution, both in the component class and the resolver class. Data can't be refreshed from params updates with this approach.

Plunker: http://plnkr.co/edit/jpntLjrNOgs6eSFp1j1P?p=preview Source material: https://github.com/angular/angular/commit/f2f1ec0#diff-a19f4d51bb98289ab777640d9e8e5006R436

Spongin answered 11/7, 2016 at 17:59 Comment(5)
Could you import a service import { HeroService } as heroSvc from './hero.service' to the route and do resolve: { heroes: heroSvc.getHeroes() } ?Neoimpressionism
@Neoimpressionism you can not. Whatever goes into resolve is used as a token for DI. This is usually a class, but can be also a string or even a function reference. But because you need to provide it first, it wouldn't work the way you intent.Ingratitude
Is it possible to inject hero as parameter directly into the constructor?Insecurity
"Data can't be refreshed from params updates with this approach." Which approach would work for params updates?Nonlinearity
I answered my own question below!Nonlinearity
U
9

https://angular.io/docs/ts/latest/api/router/index/Resolve-interface.html "resolve" has been brought back to angular2 router, but the documentation is sparse.

Example:

class TeamResolver implements Resolve {
  constructor(private backend: Backend) {}
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<any> {
    return this.backend.fetchTeam(this.route.params.id);
  }
}
bootstrap(AppComponent, [
  TeamResolver,
  provideRouter([{
    path: 'team/:id',
    component: TeamCmp,
    resolve: {
      team: TeamResolver
    }
  }])
);
Uncinate answered 5/7, 2016 at 13:16 Comment(7)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewFoch
Thank you, @RichardTelford! Still learning the best practices here. Wonder why I've been using stackoverflow in read-only mode for a over a decade... :)Uncinate
Ok, this shows how to make the query before the component is instantiated, but how/where do you access the resolved value in the component?Kep
I think the value of 'team' in the code sample gets injected into the TeamCmp component directly (public variable in the component's constructor). I have given up with the Resolve for now, until it's not experimental anymore. I also think, the TeamResolver class must be marked @Injectable. This got the class recognized and the 'resolve' function actually ran for me. However, even though I'm returning an instance of an Observable from it, my component wouldn't get initialized or shown in the outlet. I'm going to wait for the official update from the Angular team on this one.Uncinate
I found the resolved values in the ActivatedRoute which you must inject into your component. The resolved values are stored on the property: route.snapshot.data. I too am giving up on resolves for the moment. In my case I was expecting the resolve to happen before the canActivate method of router guards, but the guard always gets called first :(Kep
@KishoreRelangi import {Resolve} from "@angular/router";Harleyharli
For anyone looking here: retrieving the resolved data in the component does not work. Will result in an error.Carthusian
T
5

You can create your Resolver in Angular2+ and apply it to the router quite easily. look at the below, here is the way to create the Resolver in Angular:

@Injectable()
export class AboutResolver implements Resolve<Message> {

  constructor(private aboutService: AboutService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    const id = route.params['id'];
    return this.aboutService.getById(id);
  }
}

then in the router config:

export const Routes: RouterConfig = [
  { path: 'about',  component: AboutComponent, resolve: { data: AboutResolver } }
]; 

and finally in your component:

ngOnInit() {
  this.route.data.subscribe((data: { about: About }) => {
    this.about = data.about;
  });
}
Tien answered 22/6, 2017 at 13:45 Comment(0)
N
4

@AndréWerlang's answer was good, but if you want the resolved data on the page to change when the route parameter changes, you need to:

Resolver:

@Injectable()
export class MessageResolver implements Resolve<Message> {

  constructor(private messageService: MessageService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Message> {
    const id = +route.params['id'];
    return this.messageService.getById(id);
  }
}

Your component:

ngOnInit() {
  this.route.data.subscribe((data: { message: Message }) => {
    this.message = data.message;
  });
}
Nonlinearity answered 6/3, 2017 at 2:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.