Cleanse your Angular components of Route specific code

All too often I become frustrated when building an authentication component. The asynchronous flow is hard to manage and it makes my template a mess of ngIfs and Elvis operators:

{{ (user | async)?.uid }} 

The problem is always same. I’m mixing too many concerns in one place. Organizing the right code in the right place is what makes a happy codebase.

The solution: Store route specific code in Angular’s Route Guards.

Route Guards

1. Identify Route code in your component

The AngularFireAuth service is a great match for routing code. It contains information about a current user, whether they are coming from a redirect, and all sorts of other things.

Take a simple signInWithRedirect example:

import { Component, OnInit } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Router } from '@angular/router';
import * as firebase from 'firebase/app';

@Component({
  selector: 'app-login',
  template: `
    <button class="btn" (click)="auth()">
       Login
    </button>
  `,
  styleUrls: ['./login.scss']
})
export class LoginComponent implements OnInit {

  constructor(
    private afAuth: AngularFireAuth,
    private router: Router
  ) { }

  ngOnInit() {
    this.afAuth.getRedirectResult().then(result => {
      if(result) {
        this.router.navigateByUrl('profile');
      }
    });
  }

  auth() {
    const provider = new firebase.auth.GoogleAuthProvider();
    this.afAuth.auth.signInWithRedirect(provider); 
  }

} 

Here’s what’s going in this example:

  1. The user is logged in with the auth() method.
  2. The signInWithRedirect() method triggers a redirect to sign in with Google.
  3. After the user selects their account, they are taken back to this component.
  4. The getRedirectResult() promise in ngOnInit() resolves the logged in user.
  5. The router navigates the user to the profile page via the navigateByUrl() method.

This isn’t a problem with one component. However, imagine having to manage redirects in multiple components.

The code in ngOnInit belongs in a Route Guard. This will clean up the component of routing logic and remove the Router dependency entirely.

2. Create a Route Guard

A Route Guard is used for all sorts of useful things. In this case a guard can handle the redirect code.

@Injectable()
export class RedirectGuard implements CanActivate {

  constructor(
    private authService: AngularFireAuth,
    private router: Router
  ) {}

  canActivate() {
    this.authService.auth.getRedirectResult().then(result => {
      if(result.user != null) {
        this.router.navigateByUrl('profile');
        return false;
      }
      return true;
    });
  }

} 

The canActivate method determined if the user has access to the route. Here’s what happens:

  1. The user visits the route.
  2. AngularFireAuth checks if a redirect result exists.
  3. If a user comes back from a redirect, route to the 'profile' route.
  4. Else, go to the current route.

3. Set up the route

This code is now reusable. The Angular Router lets you specify which routes you want to listen for a redirect.

import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { RedirectGuard } from './redirect.guard';

const ROUTES: Routes = [
  { 
    path: '', 
    component: LoginComponent, 
    canActivate: [RedirectGuard] 
  },
  { 
    path: 'profile', 
    loadChildren: 'app/profile/profile.module#ProfileModule'
  }
];

const AppRoutes = RouterModule.forRoot(ROUTES);

export { AppRoutes } 

4. No more routing code in the component

Now the LoginComponent is free of routing code:

@Component({
  selector: 'app-login',
  template: `
     <button class="btn" (click)="auth()">
       Login
     </button>
  `,
  styleUrls: ['./login.scss']
})
export class LoginComponent {

  constructor(private afAuth: AngularFireAuth) { }

  auth() {
    const provider = new firebase.auth.GoogleAuthProvider();
    this.afAuth.auth.signInWithRedirect(provider); 
  }

} 

No more async flow mess. The right code is organized in the right place. You can add the RedirectGuard to more routes as the app grows, which keeps the complexity out of your components.