Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: Inject a dependency into @CanActivate?

In Angular 2, you can specify a @CanActivate annotation for a component where you can determine if the component should be activated or not. The reason it's not an interface is because the callback is called before the component is even instantiated. The problem is, I can't figure out a way to get dependencies injected into that callback. And I need my service that tells me whether I'm logged in or not (and as whom) to determine whether routing to a particular component is allowed or not.

How can I inject a dependency into a @CanActivate callback? I'm using ES5, and this doesn't work:

app.ListsComponent = ng.router.CanActivate([
    app.SessionService,
    function(session, instruction) {
        console.log(session);
        return true;
    }
])(app.ListsComponent);

Edit: Alternatively, I can use the routerOnActivate lifecycle event on the component, and use this.router.navigate to send the user away if they're not supposed to be there. The downside there is that it breaks browser history: If I redirect you asynchronously every time you arrive at a particular URL, you can't use the Back button in your browser very usefully. Is there a way to have router.navigate use history.replaceState instead of history.pushState for this kind of situation?

like image 781
Ben Dilts Avatar asked Jan 10 '16 23:01

Ben Dilts


Video Answer


2 Answers

Most solutions here will present problems with loading sub-dependencies from elsewhere in the hierarchy, because they create a new injector. Also, this results in additional (non-shared) services being instanced. I recommend following the pattern provided by Brandon in https://github.com/angular/angular/issues/4112

He references this Plunk: http://plnkr.co/edit/SF8gsYN1SvmUbkosHjqQ?p=preview

Its main idea is a singleton injector, which he saves when the app initializes. This provides access to the root dependencies you already have configured, and further allows your services to be shared as a singleton as they were probably intended:

import {Injector} from 'angular2/angular2';
let appInjectorRef: Injector;

export const appInjector = (injector?: Injector):Injector => {
    if (injector) {
      appInjectorRef = injector;
    }

    return appInjectorRef;
};

bootstrap([ServiceYouNeed]).then((appRef) => {
    // store a reference to the injector
    appInjector(appRef.injector);
});
like image 115
shannon Avatar answered Sep 21 '22 18:09

shannon


You have to inject it using injector. Just a quick copy paste from a project I'm doing:

@CanActivate((next: ComponentInstruction, prev: ComponentInstruction) => {

  console.info('CanActivate hook! - can return boolean or Promise');

  var injector = Injector.resolveAndCreate([HTTP_PROVIDERS, YourService]);
  var yourService = injector.get(YourService);
  // do something with yourService
  return new Promise(function(resolve, reject) {
      resolve(true);
  });

})

HTTP_PROVIDERS you have to pass along if your service is for example injecting the HTTP service in the constructor.

I use it to put an observable on the params of the next object. And the next object is your next "Component/state":

@CanActivate((next: ComponentInstruction, prev: ComponentInstruction) => {

  console.info('properties component CanActivate hook! - can return boolean or Promise');
  var injector = Injector.resolveAndCreate([HTTP_PROVIDERS, PropertiesService]);
  var propertiesService = injector.get(PropertiesService);
  return new Promise(function(resolve, reject) {
      next.params['properties'] = propertiesService.getProperties();
      resolve(true);
  });

})

The PropertiesService calls a backend and returns an Observable that represents the data with properties

like image 21
Sam Vloeberghs Avatar answered Sep 21 '22 18:09

Sam Vloeberghs