Functional programming with the Angular router
How to implement CanActivate and Resolve in a functional style.
To demonstrate the implementation idea, I’ve set up a minimalistic demo application. The source code is available to you on GitHub. Please find the link at the bottom of the story. Now, let’s dive in!
A glance at the classic object-oriented style
The traditional approach in Angular is to implement routing guards as classes. Let’s just take a quick look at that so that we can compare both approaches — object-oriented and functional:
The class FooGuard
implements the interface CanActivate
and the only method defined by that interface, namely canActivate()
.
The implementation class is annotated with@Injectable()
— in TypeScript jargon, the class is actually decorated but it works pretty much the same way as annotations do in other oop languages.
To make things work, the class is hooked into Angular’s dependendency injection and routing framework by its class token. In JavaScript land, FooGuard
is a reference to the constructor function and thus we
write canActivate: [ FooGuard ]
as well as providers: [ FooGuard ]
.
Implementation of `CanActivate` in functional programming style
Now, let’s implement the same with a functional approach:
Here, barGuard()
serves as a factory function and returns a lambda expression, a so-called arrow function in TypeScript terms. The call signature of the lambda is equivalent to that defined by the function canActivate()
that we have seen before.
The guard implementation must yet be hooked into Angular’s dependency injection. For that purpose, a factory provider will be registered with:
{ provide: BAR_GUARD_TOKEN, useFactory: barGuard }
That token BAR_GUARD_TOKEN
is also referenced in the route definition.
When activating the /bar route, Angular resolves the token, executes the factory function, and calls the lambda expression returned by the factory.
The return value of the lambda expression tells the router to either allow (returning true
) or deny navigation (returning false
). If allowed, a component shows up on the screen telling you that “bar works!”.
Using the guard in a route definition looks a little bit different for the object-oriented and the functional approach. The following code snippet gives us a good understanding and shows how things are wired up:
Advanded functional programming with the Angular router
In the same way that CanActivate
guards are implemented in functional programming style, it’s also possible to implement Resolve
.
Again, write a factory that returns a lambda expression whose call signature is equivalent to resolve()
defined by the Resolve
interface.
The function needs to registered with Angular’s dependency injection and routing framework by its corresponding token FOOBAR_RESOLVER_ALPHA
.
In object-oriented style, it’s possible to inject services into CanActivate
andResolve
implementations, since the @Injectable()
decorator makes the class eligible for constructor injection.
In functional programming style, we can do the same thing by passing dependencies as arguments to the factory function. This works by altering the factory provider:
{ provide: FOOBAR_RESOLVER_BETA,
useFactory: foobarResolverBeta,
deps: [ RemoteDataService ] }
An instance of RemoteDataService
is then injected to the factory function and the lambda-style resolver is going to return data by delegating to the service.
Like in the first example, the functions need to be registered for Angular’s dependency injection. The relevant code snippet is:
Try it out!
If you like to see the application with your own eyes or just like playing around, here is the source code:
What do you guys think? Do you like the functional programming approach? Do you prefer the object-oriented way? Let me know!