60. Implementing Authentication

auth.service.ts

import { AuthData } from './auth-data.model';
import { User } from './user.model';

import { Subject } from 'rxjs/Subject';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable()
export class AuthService {

    authChange = new Subject<boolean>();
    private user: User;

    constructor(private router: Router) {}

    registerUser(authData: AuthData): void {
        this.user = {
            email: authData.email,
            userId: Math.round(Math.random() * 10000).toString()
        };
        this.authSuccessfully();
    }

    login(authData: AuthData): void {
        this.user = {
            email: authData.email,
            userId: Math.round(Math.random() * 10000).toString()
        };
        this.authSuccessfully();
    }

    logout(): void {
        this.user = null;
        this.authChange.next(false);
        this.router.navigate(['/login']);
    }

    getUser(): User {
        return { ...this.user };
    }

    isAuth(): boolean {
        return this.user != null;
    }

    private authSuccessfully(): void {
        this.authChange.next(true);
        this.router.navigate(['/training']);
    }
}

header.component.ts

import { Component, OnInit, EventEmitter, Output, OnDestroy } from '@angular/core';
import { AuthService } from 'src/app/auth/auth.service';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit, OnDestroy {

  @Output() sidenavToggle = new EventEmitter<void>();
  isAuth = false;
  authSubscription: Subscription;

  constructor(private authService: AuthService) { }

  ngOnInit(): void {
    this.authSubscription = this.authService.authChange.subscribe(authStatus => {
      this.isAuth = authStatus;
    });
  }

  onToggleSidenav(): void {
    this.sidenavToggle.emit();
  }

  onLogout(): void {
    console.log('--- header onLogout');
    this.authService.logout();
  }

  ngOnDestroy(): void {
    this.authSubscription.unsubscribe();
  }
}
  • 61. Routing & Authentication

62. Route Protection

auth.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate {

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

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
        boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {

        if (this.authService.isAuth()) {
            return true;
        } else {
            return this.router.navigate(['/login']);
        }
    }

}
  • 63. Preparing the Exercise Data

  • 64. Injecting & Using the Training Service

65. Setting an Active Exercise

training.service.ts

import { EventEmitter, Injectable, Output } from '@angular/core';
import { Exercise } from './past-trainings/exercise.model';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class TrainingService {

    exerciseChanged = new Subject<Exercise>();

    availableExercises: Exercise[] = [
        { id: 'crunches', name: 'Crunches', duration: 30, calories: 8 },
        { id: 'touch-toes', name: 'Touch Toes', duration: 180, calories: 15 },
        { id: 'side-lunges', name: 'Side Lunges', duration: 120, calories: 18 },
        { id: 'burpees', name: 'Burpees', duration: 60, calories: 8 }
    ];

    private exercises: Exercise[] = [];
    private runningExercise: Exercise;

    startExercise(selectedId: string): void {
      this.runningExercise = this.availableExercises.find(ex => ex.id === selectedId);
      this.exerciseChanged.next({ ...this.runningExercise });
    }

    completeExercise() {
      this.exercises.push({
        ...this.runningExercise,
        date: new Date(),
        state: 'completed'
      });
      this.runningExercise = null;
      this.exerciseChanged.next(null);
    }

    cancelExercise(progress: number) {
      this.exercises.push({
        ...this.runningExercise,
        duration: this.runningExercise.duration * (progress / 100),
        calories: this.runningExercise.calories * (progress / 100),
        date: new Date(),
        state: 'cancelled'
      });
      this.runningExercise = null;
      this.exerciseChanged.next(null);
    }

    getRunningExercise(): Exercise {
      return {...this.runningExercise};
    }

    getCompletedOrCancelledExercises(): Exercise[] {
      return this.exercises.slice();
    }
}
  • 66. Controlling the Active Exercise

67. Adding a Form to the Training Component

new-training.component.html

<section class="new-training" fxLayout fxLayoutAlign="center">
    <form (ngSubmit)="onStartTraining(f)" #f="ngForm">
        <mat-card fxFlex.xs="100%" fxFlex="400px">
            <mat-card-title fxLayoutAlign="center">Time to start a workout!</mat-card-title>
            <mat-card-content fxLayoutAlign="center">

                <mat-form-field>
                    <mat-select ngModel name="selectedId" placeholder="Your workout" required>
                        <mat-option *ngFor="let e of exercises" [value]="e.id">
                            {{e.name}}
                        </mat-option>
                    </mat-select>
                </mat-form-field>

            </mat-card-content>
            <mat-card-actions fxLayoutAlign="center">
                <button type="submit" mat-button [disabled]="f.invalid">Start</button>
            </mat-card-actions>
        </mat-card>
    </form>
</section>

new-training.component.ts

import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Exercise } from '../past-trainings/exercise.model';
import { TrainingService } from '../training.service';

@Component({
  selector: 'app-new-training',
  templateUrl: './new-training.component.html',
  styleUrls: ['./new-training.component.css']
})
export class NewTrainingComponent implements OnInit {

  exercises: Exercise[];

  constructor(private trainingService: TrainingService) {
    this.exercises = trainingService.availableExercises;
  }

  ngOnInit(): void {
  }

  onStartTraining(form: NgForm): void {
    this.trainingService.startExercise(form.value.selectedId);
  }
}
  • 68. Handling the Active Training via a Service

  • 69. RxJS 6 Update

70. Handling "Complete" and "Cancel" Events

current-training.component.ts

import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { TrainingService } from '../training.service';
import { StopTrainingComponent } from './stop-training.component';

@Component({
  selector: 'app-current-training',
  templateUrl: './current-training.component.html',
  styleUrls: ['./current-training.component.css']
})
export class CurrentTrainingComponent implements OnInit {

  progress = 0;
  timer: any;

  constructor(private dialog: MatDialog, private trainingService: TrainingService) { }

  ngOnInit(): void {
    this.startTimer();
  }

  startTimer(): void {
    const step = this.trainingService.getRunningExercise().duration / 100 * 1000;
    this.timer = setInterval(() => {
      this.progress += 10;
      if (this.progress >= 100) {
        this.trainingService.completeExercise();
        clearInterval(this.timer);
      }
    }, step);
  }

  onStop(): void {
    clearInterval(this.timer);
    const dialogRef = this.dialog.open(StopTrainingComponent, {
      data: {
        progress: this.progress
      }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result) {
        this.trainingService.cancelExercise(this.progress);
      } else {
        this.startTimer();
      }
    });
  }
}

current-training.component.html

<section class="current-training" fxLayout="column" fxLayoutAlign="center center">
    <mat-progress-spinner mode="determinate" [value]="progress"></mat-progress-spinner>
    <h1>{{progress}}%</h1>
    <p>Keep training!</p>
    <button mat-raised-button color="accent" (click)="onStop()">Stop</button>
</section>

71. Adding the Angular Material Data Table

past-trainings.component.html

<div fxLayoutAlign="center center">
  <mat-form-field fxFlex="40%">
    <input matInput type="text" (keyup)="doFilter($event.target.value)" placeholder="Filter">
  </mat-form-field>
</div>
<mat-table [dataSource]="dataSource" matSort>
  <ng-container matColumnDef="date">
    <mat-header-cell *matHeaderCellDef mat-sort-header>Date</mat-header-cell>
    <mat-cell *matCellDef="let element">{{ element.date | date }}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
    <mat-cell *matCellDef="let element">{{ element.name }}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="calories">
    <mat-header-cell *matHeaderCellDef mat-sort-header>Calories</mat-header-cell>
    <mat-cell *matCellDef="let element">{{ element.calories | number }}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="duration">
    <mat-header-cell *matHeaderCellDef mat-sort-header>Duration</mat-header-cell>
    <mat-cell *matCellDef="let element">{{ element.duration | number }}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="state">
    <mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
    <mat-cell *matCellDef="let element">{{ element.state }}</mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>

<mat-paginator [pageSize]="2" [pageSizeOptions]="[2, 5, 10, 20]">
</mat-paginator>

past-trainings.component.ts

import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { TrainingService } from '../training.service';
import { Exercise } from './exercise.model';
import { MatPaginator } from '@angular/material/paginator';

@Component({
  selector: 'app-past-trainings',
  templateUrl: './past-trainings.component.html',
  styleUrls: ['./past-trainings.component.css']
})
export class PastTrainingsComponent implements OnInit, AfterViewInit {

  displayedColumns = ['date', 'name', 'duration', 'calories', 'state'];
  dataSource = new MatTableDataSource<Exercise>();

  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatPaginator) paginator: MatPaginator;

  constructor(private trainingService: TrainingService) { }

  ngOnInit(): void {
    this.dataSource.data = this.trainingService.getCompletedOrCancelledExercises();
  }

  ngAfterViewInit(): void {
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
  }

  doFilter(filterValue: string): void {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }
}
  • 72. Adding Sorting to the Data Table

  • 73. Adding Filtering to the Data Table

  • 74. Data Table Filtering++

  • 75. Adding Pagination to the Data Table

  • 76. Wrap Up

Resources for this lecture

  • data-01-auth.zip

  • data-02-route-protection.zip

  • data-03-training-service.zip

  • data-04-advanced-service.zip

  • data-05-data-table.zip

  • data-06-sorting-filtering-pagination.zip