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
77. Useful Resources & Links
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