Angular 2: Global Hooks/ Events for Component Router

1,504 views
Skip to first unread message

Manfred Steyer

unread,
Dec 20, 2015, 5:38:47 PM12/20/15
to AngularJS
Hi,

is there a global hook or event, that is fired each time the Component Router tries to switch to another route? I know the livecycle-hooks that can be placed within specific components but what I'm looking for is a global event so that I'm able to prevent navigating to some routes in some circumstances.

Wishes,
Manfred

Eric Martinez

unread,
Dec 21, 2015, 11:51:30 AM12/21/15
to AngularJS
I think you're looking for CanActivate

Manfred Steyer

unread,
Dec 21, 2015, 2:16:34 PM12/21/15
to AngularJS
Hi Eric,

thanks, but this isn't excactly what I'm looking for. I'm really seeking a global event that isn't associated with a specific component. Something like a generic CanActivate that kicks in for every component or something like $stateChangeStart in UI Router.

Is there something like this?

Wishes,
Manfred

Kegham Redjebian

unread,
Dec 21, 2015, 4:39:13 PM12/21/15
to AngularJS
I believe you have to develop your own layer to manage global events.

Martin Kuhn

unread,
Dec 22, 2015, 1:14:48 AM12/22/15
to AngularJS
I can not believe that this common use case is not handled by the new router (I hope I am not wrong). Especially when you consider how long the router is in the works already...

Sander Elias

unread,
Dec 22, 2015, 5:21:40 AM12/22/15
to AngularJS
Hi Manfred, Martin.

There is a good reason there is no support for this. It leads to tightly coupled code, and the code tends to land 'far' away from where it belongs.
The easy solution is to create a service that checks the thing you need, and then just assign that to the canActivate lifetime hooks you need it.

Regards
Sander

Manfred Steyer

unread,
Dec 22, 2015, 7:09:55 AM12/22/15
to AngularJS
Hi Sander,

I think it depends on the use case and I also think the framework should give us the possibility so that we can decide. 

But I'm curious: Do you know for sure, that this is not supported?

Wishes,
Manfred

Martin Kuhn

unread,
Dec 22, 2015, 7:16:28 AM12/22/15
to AngularJS
Sorry I strongly disagree. I do the route config already on the "app" component. I don't see "coupling" -> I see this as cross cutting concern.
So I have to inject the "router logic" service into every Component and have to ask the service.

The decision should really be made by the developer. From my point of view the framework should provide the functionality.

Regards
Martin


Am Dienstag, 22. Dezember 2015 11:21:40 UTC+1 schrieb Sander Elias:

Sander Elias

unread,
Dec 22, 2015, 8:05:07 AM12/22/15
to AngularJS
Hi Manfred,

Not 100% sure for the current incarnation, but I had a similar conversation with Brian about this quite long ago. 
I will check it, to be 100% sure, and then report back here ;)

Regards
Sander

Sander Elias

unread,
Dec 22, 2015, 8:41:46 AM12/22/15
to AngularJS
Hi Manfred,

So, I did a quick check, and didn't find anything that convinces me it has changed.
I think your use case can be quickly resolved by using the routerCanDeactivate lifecycle method, of the component that hosts the components you don't want to allow.

I have gone over a large amount of use cases, and did not find a use case that really needed global routing hooks/events.  The way the router now works, makes sure the routing is very close to the component's it is involved with. This is a big win for maintainability.

Regards
Sander

Martin Kuhn

unread,
Dec 22, 2015, 9:10:55 AM12/22/15
to AngularJS
I don't see the big win for maintainability. The logic for the decision is probably in a service. So you have to inject this service into every component and implement "canActivate" . Only for calling the service which makes the decision

Regards

Sander Elias

unread,
Dec 22, 2015, 9:56:17 AM12/22/15
to AngularJS
Hi Martin,

No, you shouldn't need to inject it everywhere. Usually there is no need for that. If you make sure that at  a higher-level component the check is done. 
See, for the router your app has a tree-like structure. You can put an can(de)activate on every branch, and/or even at the base. everything above that point, can only be reached when the condition is satisfied. 

Can you provide a use-case that really demands a global handler? 

Regards
Sander

Manfred Steyer

unread,
Dec 22, 2015, 3:13:26 PM12/22/15
to AngularJS
Hi Sander,

thanks for checking this and for your answers.

I think a good use case would look like this:

- Providing metadata for components to state that they can be accessed by unauthenticated users, something like @Auth({anonymous:true})
- Using event to prevent unauthenticated users to access components without this directive or with @Auth({anonymous:false})

Wishes,
Manfred

p. s.: Of course, the real security-checks have to be performed on server-side

Sander Elias

unread,
Dec 23, 2015, 12:27:41 AM12/23/15
to AngularJS
Hi Manfred,


- Providing metadata for components to state that they can be accessed by unauthenticated users, something like @Auth({anonymous:true})
- Using event to prevent unauthenticated users to access components without this directive or with @Auth({anonymous:false})
Well, the state of a user should live in a service. You can check that state easily in the components using the canActivate hook. And indeed, writing a small decorator that does that is a good way to handle it.
That decorator can even wrap in a existing canActivate. As ng2 has no clue on how you auth works(that's a good thing!) there can not be a generic solution. 
Alternatively, You can create a split in the 'top' of your app, where you decide if an user is authed or not, and put everything that needs to know in it's own 'branch'

Regards
Sander 

Martin Kuhn

unread,
Dec 23, 2015, 1:37:27 AM12/23/15
to AngularJS
Hi Sander, 

there is no use case which demands a global handler absolutley. But it would be very convenient. 
With "inject the service in every component" I meant the main component of a certain domain (e.g. for a user profile page). And of course not the many "components which are used to assemble such a main component.
(when this is what you mean with tree like structure)

But the point is, I have no user data or user data when the user is authenticated. The user data maybe contain certain userrights. Based on the state authenticated or not, user has userright or not I want to decide...
Of course, I can inject the service which holds the user data and the decision logic in every "main" component (which needs such a decision) and call the the service (e.g. userService.isAllowed('componentName').
But I would really prefer the "global handler" way...

Regards 
Martin



Am Dienstag, 22. Dezember 2015 15:56:17 UTC+1 schrieb Sander Elias:

Manfred Steyer

unread,
Dec 23, 2015, 1:02:11 PM12/23/15
to AngularJS
Hi,

thank you both for you opinions. Thanks to Sanders, I see now, that the global event isn't absolutly neccessary, but I also feel like Martin.

We are talking about cross-cutting-concerns here. Devs see for years, that it isn't the best idea, to put such concerns into every piece of code. That's why, we have concepts, like interceptors or global events. In the mentioned sample, we could use such concepts to implement a good default behavior for all components, that have not been annotated.

Wishes,
Manfred

Brian Troncone

unread,
Jan 8, 2016, 12:21:13 PM1/8/16
to AngularJS
Sander,

One example use-case shows itself in a business application I am building, which requires role based authentication throughout the navigation structure. For example, each user is only allowed access to certain sections of the site after they authenticate. The active user's roles are stored in an Angular 2 service. I am not sure how to best accomplish a valid role check based on the methods you suggest. Some things I have tried:

1.) Creating a custom 'Authenticate' decorator to check logged in user's roles v. valid roles passed to decorator. The problem is there is no way to access the app-wide injector outside a component, therefore I cannot obtain the correct instance of my service containing the current users roles.

2.) Using @CanActivate, I have not found a way to gain access to the correct application services, similar to problem described in #1. If you do not have access to services here, how useful is this method?

3.) Subscribe to hooks in higher-level component which encapsulates the remaining application components. I am not sure the best method to accomplish this because my entire application needs these checks performed at every route change and I am only current using 1 'router-outlet'. 

Any advice or insight you can give on this issue would be greatly appreciated. I feel this is a scenario many business applications will encounter and it would be good to know a suggested 'best practice' for solving this problem. Thanks in advance!

Martin Wawrusch

unread,
Jan 8, 2016, 12:39:09 PM1/8/16
to AngularJS
I worked around the 1 and 2 problem with the services by using a global variable being set in the constructor of the services needed, - UGLY as hell and completely defeats the purpose of DI, but works for the time being. 

That said I very much agree with you that security is a cross cutting concern that should not be handled at the component level for larger apps. One angular 1 dashboard app that we have has 124 routable components - that's a lot of CanActivates right there...

Brian Troncone

unread,
Jan 8, 2016, 12:57:30 PM1/8/16
to AngularJS
Martin,

Thanks for your reply. We did something similar by setting a static on the service to an instance of itself when constructed. As you said though, this sort of thing is hideous and breaks DI so I'm really hoping Sander can instruct us on a better solution, or one in the works. I completely agree that this should not be handled at the component level for large applications.

Barry Rowe

unread,
Jan 8, 2016, 1:33:43 PM1/8/16
to AngularJS
Sander,

You suggest that the state should live in its own service (agreed) and that you can write your own decorator to check that:

 Well, the state of a user should live in a service. You can check that state easily in the components using the canActivate hook. And indeed, writing a small decorator that does that is a good way to handle it.
That decorator can even wrap in a existing canActivate

Do you have an example of this? From what we've tried there is no way to get to the App-level Injector to get the proper instance of your service(s) from within a decorator. 

There is also a @CanActivate decorator in angulr2, but we can't see how you would access your services from it. I don't see how that decorator can be useful if you have no access to service instances.

Thank you
Reply all
Reply to author
Forward
0 new messages