question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Router: Design for routerCanActivate, an injectable alternative to @CanActivate

See original GitHub issue

This issue describes a design for an injectable alternative to @CanActivate

It is both a fresh start and a continuation of the conversations associated with issues #748 and #4112 and tangentially in #7256, #2965, #7091


I’ve been talking to the team about these router hook methods. I have a good feeling about an emerging design that should arrive before RC. I thought I’d share it here so we can get some feedback.

CAVEAT: this is Ward’s interpretation of what he thinks he heard which may vary from the team’s interpretation and is not necessarily what will happen.

As I understand it, CanActivate will become an instance method, probably named routerCanActivate.

The catch is that we implement routerCanActivate on (what I call) a _routing component_: a component with an attached router and routes and whose template includes a <router-outlet>.

We didn’t talk about it but I think Angular should ignore it on a non-routing component or perhaps throw an error.

The router will consult this method before pursuing any of the routes that would populate that <router-outlet>. It gates navigation to any of the routes configured for this component’s router.

To be clear, it has no role in allowing/preventing navigation to the routing component itself; only navigation to the routes configured for this routing component’s <router-outlet>.

As an instance method, the routerCanActivate has access to anything we inject into its routing component. The injector (and its scope) is that of this routing component.

I think this addresses many concerns:

  1. We can inject anything we need without crazy hacks
  2. We can scope injection at an appropriate level in the component tree. We don’t have to capture the root injector and hope that the service we need has been provided there. We can access services injected “down here”, at the local router configuration level.
  3. We can make decisions about whether to allow navigation to a lazy loaded component. Before we would have had to load that component and call its @CanActivate, perhaps only to belatedly disallow it.

I had a few initial objections:

  1. I didn’t think the routing component should be responsible for can activate decisions about all of its routes.
  2. Why should it know all of the details about whether a route’s navigation should be allowed? That’s a decision that belongs to the destination component, right?
  3. Some people have used CanActivate to pre-load data from the server for the destination component. There won’t be an obvious way to do that with this design which doesn’t provide a way to deliver a payload to the destination component.
  4. The can activate logic has access to services at the routing component level and above but not the services (if any) that will be provided by (and therefore scoped at) the destination component itself. That could be too limiting.

Here is why these objections no longer trouble me.

  1. If deciding is a burden, the routing component can delegate that burden to a custom injected CanActivateService, scoped to the routing component itself. That service can inject much of what it needs to make decisions. The routing component can stay lean. This is our decision as application developers; Angular 2 neither requires it nor blocks it. I like that.

  2. While I like the idea that the can activate logic is part of the development of the destination component, that logic doesn’t have to be inscribed within the destination component class. We can come up with other patterns that will work well, particularly in concert with the CanActivateService approach I just mentioned. I think we can get the allocation of responsibilities right without too much friction. And this approach does put us in position to gate navigation to a lazy-loaded destination component which I think is the more important benefit.

  3. Actually, I always thought using can activate logic to pre-load data was a hack. Many folks did that kind of thing with resolve in Angular 1. I never liked it. Please don’t bring back resolve!

    But like/dislike aside, we don’t need it in Angular 2. In Angular 2, we have both the routerOnActivate and the ngOnInit hooks (missing in Angular 1) which are fine places to pre-load data.

  4. I couldn’t think of a use case in which it was essential that we consult a service instance created just for the destination component. OTOH, if we could do that, we’d be faced with the obligation to create such a service which brings unwanted complexity of its own.

    For example, if we allow navigation, we have to make sure that service (whatever it is), is ultimately injected into the destination component. There is no facility for doing that kind of thing today. Today we can’t access the internal steps that go into hydrating a component … and I rather doubt I’d want to risk what comes with breaking into that sausage factory.

    What if we created that service and then rejected the navigation? What did that service do? Did it know it was going to be created and then thrown away? Does it need to be cleaned up before being thrown away. It’s a horrible can of worms.

In the end, I think this design will work well for us. It’s safe, clean, easy to understand once explained.

Angular consults a routing component’s routerCanActivate method before navigating to any of the routes defined for that component’s router.

I don’t know what the method parameters will be. Obviously something about where the router is going. This is a good time for you all to chime in.

p.s.: I don’t know what will become of the current @CanActivate decorator. I suppose it could live on as a vestigial relic. Personally, I’d vote for it to be deprecated immediately and removed before release.

p.p.s.: I have stopped thinking that the routerOnActivate method is or could be a substitute for routerCanActivate (quite apart from the fact that it doesn’t work as a substitute today).

It should not be involved in the decision about whether we should navigate to this component. It should concentrate on what to do now that we’re here. And in that respect it should differentiate from its companion hook method, routerOnReuse, which concerns what to do now that we’re back.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:11
  • Comments:40 (20 by maintainers)

github_iconTop GitHub Comments

10reactions
robwormaldcommented, Mar 27, 2016

I’ve never particularly liked the coupling between a component and the router. I’d prefer to have these sorts of things in the RouteConfig:

@RouteConfig([
  {
    name: 'AuthedRoute',
    path: '/auth',
    component: AuthComponent,
    canActivate(){  return Promise.resolve('ok') }
  }
])

For things that require services:

@RouteConfig([
  {
    name: 'AuthedRoute',
    path: '/auth',
    component: AuthComponent,
    canActivate: [(authService, routeParams) => authService.check(routeParams.get('id')), AuthService, RouteParams]
  }
])

The actual activation happens scoped to the router outlet, which appears to have access to the appropriate injector. Just a thought, seems to make things more reusable.

3reactions
wardbellcommented, Apr 4, 2016

@CaptainCodeman - You make a persuasive argument for evaluating the can activate logic within the target component. Those on the other side of that argument prefer to block activation earlier, especially because the route may involve async loading which they feel is too heavy a price to pay for a failed route even if failed routes are rare. FWIW, the issue has been decided and we do well to move on.

@cquillen2003 - I don’t follow you. I was reflecting the argument in favor of writing router configuration within a component rather than in some other place. If you’re writing “a component without a router” it’s all moot, right? There’s nothing to configure and nothing to put in (or keep out) of the component.

@escardin As attractive as using inputs sounds, it doesn’t hold up in many common use cases. It’s not clear how route or query string parameters should align with input properties nor how the URL material that doesn’t match input properties should become available to the component. I fear for the complexity of whatever might be proposed for matching input properties to parameters buried in a URL.

You may have neglected the critical role of the query string in a URL. These shouldn’t be ignored nor can they be parsed into input properties. I believe this is the essence of @choeller’s critique of your proposal.

Meanwhile, all of the tried-and-true routers in my experience keep the routing constructs separate from the component/controller. We know this separation works.

I think you misunderstand the role of canReuse. It participates in the router’s decision about whether a component instance can be recycled or must be discarded and recreated. There is no equivalent among the component lifecycle hooks.

The component lifecycle hooks are no substitute for routerOnActivate and routerOnReuse. They provide valuable information about the navigation to and from the target component that is simply not available any other way, neither through the component hooks nor through input params.

To be perfectly honest, worthy as some of these ideas may be, they are too nebulous and incomplete. They are very far from addressing concretely the routing scenarios we know we need to handle.

You may disagree. That’s cool. I don’t wish to be cruel and hard-hearted but … these ideas arrive too late. At this point in the game, the design of the router has moved on and I think I can say definitively that

  • it won’t be template-driven; the @petebacondarwin cat is dead (apologies to Schrödinger)
  • @CanActivate will become something like a canActivateChildRoutes instance method of a routing component.
  • the routing hooks routerOnActivate and routerOnReuse will live on
  • routing parameters will live on (although they don’t have to be injected as they are available in the instruction parameters of the router lifecycle hooks).
  • input properties will remain independent of the routing structure
  • components that need to know information derived from navigation will have to become aware of the routing subsystem.

It’s time to move on.

It may also be worth remembering that the Component Router is an optional subsystem. It’s not part of the Angular core. If it sucks, Angular will survive, someone will build a better one, and we’ll all move to that.

My personal bet is on the Component Router which I feel soon will overcome its current, most urgent defects.

Read more comments on GitHub >

github_iconTop Results From Across the Web

alternative way for canActivate in angular - Stack Overflow
I think the best way to prevent the user from routing if not logged in is using sessionStorage since it is specific ...
Read more >
Implementing Route Protection in Angular using CanActivate
This interface is implemented by a class that determines whether the router can activate the route or not. The following snippet shows the ......
Read more >
Resolve a value *before* activating a route in Angular 2
Router : Design for routerCanActivate, an injectable alternative to @CanActivate. I have seen people write their data into a property of the next...
Read more >
Angular Router Guards Pt.1: CanActivate vs CanActivateChild ...
Angular Router Guards are very powerful feature which gives you control over access to some certain route. In this video tutorial we will ......
Read more >
Router Guards • Angular - codecraft.tv
CanActivate. Guards are implemented as services that need to be provided so we typically create them as @Injectable classes. Guards ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found