Lesson Monday

In the last lesson, we experimented with two ways of authorizing content: first we added redirects and then we used *ngIf to hide and show authorized content. In this lesson, we’ll explore Angular route guards, which will allow us to protect our application in a more thorough and sophisticated way.

We’ll focus on a guard type called CanActivate, which determines whether a route can be activated when a user tries to access it. We’ll add a conditional within our CanActivate method to determine whether a user is signed in via Firebase authentication. If our route guard returns true, then the user will be able to access the route; however, if the route returns false, access will be denied. We can also add other code within our conditional, including redirects or an alert message. Let’s do that now.

Create a service

First, we need to create a new service that will hold our guard logic:

$ ng g service auth-guard.service

(Note: You may want to organize your guard in its own directory, just as we did with our authentication service. That’s up to you.)

Let’s also add our service to app.module.ts so we can use it throughout our application. First, we’ll import it:

src/app/app.module.ts
...
import { AuthGuard } from './auth-guard.service';
…

Next, we’ll add the service to the list of providers in app.module.ts:

src/app/app.module.ts
…
providers: [AuthGuard],
…

Let’s make one other change to our code; in app.component.ts, comment out or remove the line this.router.navigate(['public’]);. We’ll use our guard to create redirects instead, so we don’t want the code in our root component to override it.

Now we’re ready to add some code to auth-guard.service.ts:

src/app/auth-guard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private router: Router) {}

  canActivate() {

  }
}

We start by importing the modules we need for our service. We’ve used Injectable before for other services. CanActivate will allow us to use the canActivate() method to set up rules for our routes. We’ll also use Router so we can incorporate redirects as necessary.

We’ve also added a canActivate() method which is currently empty. Remember, if this method returns true, the user can access the route. If the method returns false, the user won’t be able to access the route. canActivate() always needs to resolve to a boolean value. Let’s start by forbidding everyone from accessing the route:

src/app/auth-guard.service.ts
...
  canActivate() {
    alert("Access denied.")
    return false;
  }
...

While this is a very effective way of ensuring that no one visits the route, it defeats the purpose of having a route at all. Someone should be able to visit the route, even if it’s only for signed-in users or an admin.

Making our Service Accessible Everywhere

Let’s use our authentication service to determine whether the user is signed in or not. Essentially, we need our guard service to have access to our authentication service. However, we can’t add providers to a service. Instead, we’ll add the service and the provider to the root module so that it will be available throughout the application, including in our guard service.

Add this to the root module:

src/app/app.module.ts
import { AuthenticationService } from './authentication/authentication.service';
...
providers: [AuthGuard, AuthenticationService],
...

Now let’s add the code we need to make our guard service work.

src/app/auth-guard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

import { AuthenticationService } from "./authentication/authentication.service";

@Injectable()
export class AuthGuard implements CanActivate {


  constructor(private router: Router, public authService: AuthenticationService) {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.authService.afAuth.authState
                .take(1)
                .map(authState => !!authState)
                .do(authenticated => {
                  if (!authenticated) {
                      alert("Access denied.");
                      this.router.navigate(['public']);
                      return false;
                  } else {
                    return true;
                  }
                });
  }
}

We’ve added a lot of new code here. In addition to importing our authentication service, we’re also importing key functionality from RxJS. Let’s breakdown what’s new.

RxJS is a library that’s specifically used for working with observables. In addition to importing Observable, we import three operators (the RxJS term for functions).

We also import ActivatedRouteSnapshot and RouterStateSnapshot. The former contains the route that will be activated (if the user is allowed to access the route), while the latter contains the RouterState to be activated. We don’t actually need these snapshots for our code to run; however, if we did need information about the current route, these snapshots would be very useful. They’re included here because they’re common in route guards and you may find yourself in a situation where you need them.

Within canActivate(), declare our method signature as an Observable<boolean> because we expect the method to return an observable value. Next, we return our function. We need to use return or we’ll get the following error: A function whose declared type is neither 'void' nor 'any' must return a value. By adding return, we ensure that our function will always return a value.

Onto our RxJS methods, which all act on observables. In our case, the observable is the Firebase authentication state.

RxJS Operators

take will grab only the first value emitted from an observable. (We could pass in a larger integer as an argument if we wanted more than just the first value, but there’s no reason we’ll need additional values here.)

map functions much like the JavaScript map function we’ve used in the past. The key difference is that it will map observable values and then emit these values as a new observable.

So why do we map authState to !!authState? This is just a convoluted way of forcing authState into a boolean value. By mapping authState so that it’s not not authState, we’re literally doing a type conversion into a boolean observable.

Then we use do. do will act on each element emitted by our observable; in this case, the function acts once the boolean conversion is complete. At this point, our application can determine whether the user should be allowed access to the route or not.

If a user isn’t authenticated, an “Access Denied” alert will pop up and the user will be redirected to the ‘public’ route. If the user is authenticated, the private route will be activated.

And that’s it. We now have a working route guard. In addition to canActivate, there are other route guards you can work with as well. Try using canActivateChild if you have child components along with a parent private or admin component.

For more information on guard types, see the Angular documentation.

Observables versus Promises

We’ll end this lesson with a note about observables and promises.

You aren’t expected to have an in-depth understanding of RxJS at this point, but you should understand the key difference between an observable and a promise. This will at least clarify why we’re using RxJS in our code instead of creating and resolving a promise.

Remember, a promise is an object that represents an operation that hasn’t been fulfilled yet. It can be pending, fulfilled or rejected. However, a promise can only handle one event. Once the promise is resolved (either fulfilled or rejected), it will remain resolved forever. It is immutable.

An observable, on the other hand, can handle many events (or none at all). It operates like a stream, providing data over time instead of just once. Since Firebase captures changes in data in real time, our applications should also respond to these changes in real time as well. It makes sense that Firebase uses the observable design pattern; as a result, we need to use methods that work with observables. In fact, Angular uses the observable design pattern heavily as well, and if you take the time to explore and understand this design pattern further, you’ll have a better understanding of Angular as well.