Tutorial built with Angular 7.1.4 and Webpack 4.28
Other versions available:
- React - Role Based Authorization Tutorial with Example
Vue . js - Role Based Authorization Tutorial with Example
In this tutorial we'll go through an example of how you can implement role based authorization / access control using Angular 7 and TypeScript . The example builds on another tutorial I posted recently which focuses on JWT authentication in Angular 7, this version has been extended to include role based authorization / access control on top of the JWT authentication.
The tutorial example is pretty minimal and contains just 3 pages to demonstrate role based authorization in Anglar 7 - a login page, a home page and an admin page. The example contains two users - a Normal User who has access to the home page, and an Admin User who has access to everything (home page and admin page).
The project is available on GitHub at https://github.com/cornflourblue/angular-7-role-based-authorization-example.
Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/angular-7-role-based-authorization-example)
Update History:
- 04 Jan 2019 - Added auto-logout on 403 Forbidden response to be compatible with
ASP.NET Core 2 API (in addition to 401 Unauthorized), and updated to Angular 7.1.4 - 22 Nov 2018 - Built with Angular 7.1.0
Running the Angular 7 Role Based Authorization Example Locally
The tutorial example uses Webpack 4 to transpile the TypeScript code and bundle the Angular 7 modules together, and the webpack dev server is used as the local web server, to learn more about using webpack with TypeScript you can check out the webpack docs.
- Install NodeJS and NPM from https://nodejs.org/en/download/.
- Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-7-role-based-authorization-example
- Install all required
npm packages by running
from the command line in the project root folder (where the packagenpm install. json is located). - Start the application by running
from the command line in the project root folder.npm start - Your browser should automatically open at
http://localhost:8080
with the login page of the demo Angular 7 role-based authorization app displayed.
Running the Tutorial Example with a Real Backend API
The Angular 7 role based access control example app uses a fake / mock backend by default so it can run in the browser without a real api , to switch to a real backend api you just have to remove or comment out the line below the comment in the
// provider used to create fake
backend
located/src/app/app. module. ts
file.
You can build your own backend api or start with one of the below options:
- To run the Angular 7 role based auth example with a real backend API built with Node.js follow the instructions at Node.js - Role Based Authorization Tutorial with Example API
- For a real backend API built with ASP.NET Core 2.2 follow the instructions at ASP.NET Core 2.2 - Role Based Authorization Tutorial with Example API
Angular 7 Role Based Access Control Project Structure
The project and code structure of the tutorial mostly follows the best practice recommendations in the official Angular Style Guide, with a few of my own tweaks here and there.
The project structure has a folder per feature (home, admin & login), with other shared/common code (services, models, guards & helpers) placed in folders prefixed with an underscore "_" to easily differentiate between shared code and feature specific code, the prefix also groups shared component folders together at the top of the folder structure.
The
index.ts
files in each folder are barrel files that group the exported modules from a folder together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { UserService, AuthenticationService } from '@/_services'
).
A path alias '@' has been configured in the tsconfig.json and webpack.config.js that maps to the '/src/app' directory. This allows imports to be relative to the '/src/app' folder by prefixing the import path with '@', removing the need to use long relative paths like
import MyComponent from '../../../MyComponent'
.- src
- app
- _guards
- auth.guard.ts
- index.ts
- _helpers
- _models
- _services
- authentication.service.ts
- user.service.ts
- index.ts
- admin
- admin.component.html
- admin.component.ts
- index.ts
- home
- home.component.html
- home.component.ts
- index.ts
- login
- login.component.html
- login.component.ts
- index.ts
- app.component.html
- app.component.ts
- app.module.ts
- app.routing.ts
- _guards
- index.html
- main.ts
- polyfills.ts
- typings.d.ts
- app
- package.json
- tsconfig.json
- webpack.config.js
Below is all the tutorial project code along with brief descriptions of each file to explain how it all fits together.
Angular 7 Auth Guard
Path: /src/app/_guards/auth.guard.ts
The auth guard is an angular route guard that's used to prevent unauthorized users from accessing restricted routes, it does this by implementing the
CanActivate
interface which allows the guard to decide if a route can be activated with the canActivate()
method. If the method returns true
the route is activated (allowed to proceed), otherwise if the method returns false
the route is blocked.
The auth guard uses the authentication service to check if the user is logged in, if they are logged in it checks if their role is authorized to access the requested route. If they are logged in and authorized the
canActivate()
method reutrns true
, otherwise it returns false
and redirects the user to the login page.
Angular route guards are attached to routes in the router config, this auth guard is used in app.routing.ts to protect the home page and admin page routes.
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '@/_services';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthenticationService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser) {
// check if route is restricted by role
if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
// role not authorised so redirect to home page
this.router.navigate(['/']);
return false;
}
// authorised so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
Angular 7 Http Error Interceptor
Path: /src/app/_helpers/error.interceptor.ts
The Error Interceptor intercepts http responses from the api to check if there were any errors. If there is a 401 Unauthorized response the user is automatically logged out of the application, all other errors are re-thrown up to the calling service so an alert error message can be displayed to the user.
It's implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to catch all error responses from the server in a single location.
Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthenticationService } from '@/_services';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(err => {
if ([401, 403].indexOf(err.status) !== -1) {
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
this.authenticationService.logout();
location.reload(true);
}
const error = err.error.message || err.statusText;
return throwError(error);
}))
}
}
Angular 7 Fake Backend Provider
Path: /src/app/_helpers/fake-backend.ts
The fake backend provider enables the example to run without a backend / backendless, it contains a hardcoded collection of users and provides fake implementations for the api endpoints "authenticate", "get user by id", and "get all users", these would be handled by a real api and database in a production application.
The "authenticate" endpoint is used for logging in to the application and publicly accessible, the "get user by id" is restricted to authenticated users in any role, however regular users can only access their own user record whereas admin users can access any user record. The "get all users" endpoint is restricted to admin users only.
The fake backend implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to modify http requests before they get sent to the server. In this case the FakeBackendInterceptor intercepts certain requests based on their URL and provides a fake response instead of going to the server.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
import { User, Role } from '@/_models';
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const users: User[] = [
{ id: 1, username: 'admin', password: 'admin', firstName: 'Admin', lastName: 'User', role: Role.Admin },
{ id: 2, username: 'user', password: 'user', firstName: 'Normal', lastName: 'User', role: Role.User }
];
const authHeader = request.headers.get('Authorization');
const isLoggedIn = authHeader && authHeader.startsWith('Bearer fake-jwt-token');
const roleString = isLoggedIn && authHeader.split('.')[1];
const role = roleString ? Role[roleString] : null;
// wrap in delayed observable to simulate server api call
return of(null).pipe(mergeMap(() => {
// authenticate - public
if (request.url.endsWith('/users/authenticate') && request.method === 'POST') {
const user = users.find(x => x.username === request.body.username && x.password === request.body.password);
if (!user) return error('Username or password is incorrect');
return ok({
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
role: user.role,
token: `fake-jwt-token.${user.role}`
});
}
// get user by id - admin or user (user can only access their own record)
if (request.url.match(/\/users\/\d+$/) && request.method === 'GET') {
if (!isLoggedIn) return unauthorised();
// get id from request url
let urlParts = request.url.split('/');
let id = parseInt(urlParts[urlParts.length - 1]);
// only allow normal users access to their own record
const currentUser = users.find(x => x.role === role);
if (id !== currentUser.id && role !== Role.Admin) return unauthorised();
const user = users.find(x => x.id === id);
return ok(user);
}
// get all users (admin only)
if (request.url.endsWith('/users') && request.method === 'GET') {
if (role !== Role.Admin) return unauthorised();
return ok(users);
}
// pass through any requests not handled above
return next.handle(request);
}))
// call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
.pipe(materialize())
.pipe(delay(500))
.pipe(dematerialize());
// private helper functions
function ok(body) {
return of(new HttpResponse({ status: 200, body }));
}
function unauthorised() {
return throwError({ status: 401, error: { message: 'Unauthorised' } });
}
function error(message) {
return throwError({ status: 400, error: { message } });
}
}
}
export let fakeBackendProvider = {
// use fake backend in place of Http service for backend-less development
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true
};
Angular 7 JWT Interceptor
Path: /src/app/_helpers/jwt.interceptor.ts
The JWT Interceptor intercepts http requests from the application to add a JWT auth token to the Authorization header if the user is logged in. It checks if the user is logged in by getting the current user object from the authentication service.
It's implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to modify http requests before they get sent to the server.
Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthenticationService } from '@/_services';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
let currentUser = this.authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}
Angular 7 Role Model
Path: /src/app/_models/role.ts
The role model contains an enum that defines the roles that are supported by the application.
export enum Role {
User = 'User',
Admin = 'Admin'
}
Angular 7 User Model
Path: /src/app/_models/user.ts
The user model is a small class that defines the properties of a user. The
token
property is used to hold the JWT token that is returned from the api on successful authentication.export class User {
id: number;
username: string;
password: string;
firstName: string;
lastName: string;
role: string;
token?: string;
}
Angular 7 Authentication Service
Path: /src/app/_services/authentication.service.ts
The authentication service is used to login and logout of the application, to login it posts the user's credentials to the api and checks the response for a JWT token, if there is one it means authentication was successful so the user details including the token are added to local storage.
The logged in user details are stored in local storage so the user will stay logged in if they refresh the browser and also between browser sessions until they logout. If you don't want the user to stay logged in between refreshes or sessions the behaviour could easily be changed by storing user details somewhere less persistent such as session storage which would persist between refreshes but not browser sessions, or in a private variable in the authentication service which would be cleared when the browser is refreshed.
There are two properties exposed by the authentication service for accessing the currently logged in user. The
currentUser
observable can be used when you want a component to reactively update when a user logs in or out, for example in the app.component.ts so it can show/hide the main nav bar when the user logs in/out. The currentUserValue
property can be used when you just want to get the current value of the logged in user but don't need to reactively update when it changes, for example in the auth.guard.ts which restricts access to routes by checking if the user is currently logged in.import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { User } from '@/_models';
@Injectable({ providedIn: 'root' })
export class AuthenticationService {
private currentUserSubject: BehaviorSubject<User>;
public currentUser: Observable<User>;
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
this.currentUser = this.currentUserSubject.asObservable();
}
public get currentUserValue(): User {
return this.currentUserSubject.value;
}
login(username: string, password: string) {
return this.http.post<any>(`${config.apiUrl}/users/authenticate`, { username, password })
.pipe(map(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
}
return user;
}));
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
this.currentUserSubject.next(null);
}
}
Angular 7 User Service
Path: /src/app/_services/user.service.ts
The user service contains just a couple of methods for retrieving user data from the api, it acts as the interface between the Angular application and the backend api.
I included the user service to demonstrate accessing secure api endpoints with the http authorization header set after logging in to the application, the auth header is set with a JWT token in the JWT Interceptor above. The secure endpoints in the example is a fake one implemented in the fake backend provider above.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { User } from '@/_models';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) { }
getAll() {
return this.http.get<User[]>(`${config.apiUrl}/users`);
}
getById(id: number) {
return this.http.get<User>(`${config.apiUrl}/users/${id}`);
}
}
Angular 7 Admin Component Template
Path: /src/app/admin/admin.component.html
The admin component template contains html and angular 7 template syntax for displaying a list of all users retrieved from a secure api endpoint.
<h1>Admin</h1>
<p>This page can only be accessed by administrators.</p>
<div>
All users from secure (admin only) api end point:
<ul>
<li *ngFor="let user of users">{{user.firstName}} {{user.lastName}}</li>
</ul>
</div>
Angular 7 Admin Component
Path: /src/app/admin/admin.component.ts
The admin component calls the user service to get all users from a secure api endpoint and store them in a local
users
property which is accessible to the admin component template above.import { Component, OnInit } from '@angular/core';
import { first } from 'rxjs/operators';
import { User } from '@/_models';
import { UserService } from '@/_services';
@Component({templateUrl: 'admin.component.html'})
export class AdminComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getAll().pipe(first()).subscribe(users => {
this.users = users;
});
}
}
Angular 7 Home Component Template
Path: /src/app/home/home.component.html
The home component template contains html and angular 7 template syntax for displaying a simple welcome message and the current user record that is fetched from a secure api endpoint.
<h1>Home</h1>
<p>You're logged in with Angular 7 & JWT!!</p>
<p>Your role is: <strong>{{currentUser.role}}</strong>.</p>
<p>This page can be accessed by all authenticated users.</p>
<div>
Current user from secure api end point:
<ul>
<li *ngIf="userFromApi">{{userFromApi.firstName}} {{userFromApi.lastName}}</li>
</ul>
</div>
Angular 7 Home Component
Path: /src/app/home/home.component.ts
The home component gets the current user from the authentication service and then gets the current user from the api with the user service. We only really needed to get the user from the authentication service, but I included getting it from the user service as well to demonstrate fetching data from a secure api endpoint.
import { Component } from '@angular/core';
import { first } from 'rxjs/operators';
import { User } from '@/_models';
import { UserService, AuthenticationService } from '@/_services';
@Component({templateUrl: 'home.component.html'})
export class HomeComponent {
currentUser: User;
userFromApi: User;
constructor(
private userService: UserService,
private authenticationService: AuthenticationService
) {
this.currentUser = this.authenticationService.currentUserValue;
}
ngOnInit() {
this.userService.getById(this.currentUser.id).pipe(first()).subscribe(user => {
this.userFromApi = user;
});
}
}
Angular 7 Login Component Template
Path: /src/app/login/login.component.html
The login component template contains a login form with username and password fields. It displays validation messages for invalid fields when the submit button is clicked. The form submit event is bound to the onSubmit() method of the login component.
There's an info alert message above the form with the login details for two example users, a normal user in the
User
role and an admin user in the Admin
role.
The component uses reactive form validation to validate the input fields, for more information about angular reactive form validation check out Angular 7 - Reactive Forms Validation Example.
<div class="alert alert-info col">
<strong>Normal User</strong> - U: user P: user<br />
<strong>Administrator</strong> - U: admin P: admin
</div>
<h2>Login</h2>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">Login</button>
<img *ngIf="loading" class="pl-2" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
</div>
<div *ngIf="error" class="alert alert-danger">{{error}}</div>
</form>
Angular 7 Login Component
Path: /src/app/login/login.component.ts
The login component uses the authentication service to login to the application. If the user is already logged in they are automatically redirected to the home page.
The
loginForm: FormGroup
object defines the form controls and validators, and is used to access data entered into the form. The FormGroup is part of the Angular Reactive Forms module and is bound to the login template above with the [formGroup]="loginForm"
directive.import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AuthenticationService } from '@/_services';
@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
loginForm: FormGroup;
loading = false;
submitted = false;
returnUrl: string;
error = '';
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private authenticationService: AuthenticationService
) {
// redirect to home if already logged in
if (this.authenticationService.currentUserValue) {
this.router.navigate(['/']);
}
}
ngOnInit() {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
// get return url from route parameters or default to '/'
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
}
// convenience getter for easy access to form fields
get f() { return this.loginForm.controls; }
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.loginForm.invalid) {
return;
}
this.loading = true;
this.authenticationService.login(this.f.username.value, this.f.password.value)
.pipe(first())
.subscribe(
data => {
this.router.navigate([this.returnUrl]);
},
error => {
this.error = error;
this.loading = false;
});
}
}
Angular 7 App Component Template
Path: /src/app/app.component.html
The app component template is the root component template of the application, it contains the main nav bar which is only displayed for authenticated users, and a router-outlet directive for displaying the contents of each view based on the current route.
The nav bar contains links for the home page, the admin page and a logout link. The admin link is only displayed for users in the
Admin
role by using the isAdmin
getter in an *ngIf
directive. The logout link calls the logout()
method of the app component on click.<!-- nav -->
<nav class="navbar navbar-expand navbar-dark bg-dark" *ngIf="currentUser">
<div class="navbar-nav">
<a class="nav-item nav-link" routerLink="/">Home</a>
<a class="nav-item nav-link" routerLink="/admin" *ngIf="isAdmin">Admin</a>
<a class="nav-item nav-link" (click)="logout()">Logout</a>
</div>
</nav>
<!-- main app container -->
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3">
<router-outlet></router-outlet>
</div>
</div>
</div>
</div>
Angular 7 App Component
Path: /src/app/app.component.ts
The app component is the root component of the application, it defines the root tag of the app as
<app></app>
with the selector property of the @Component
decorator.
It subscribes to the
currentUser
observable in the authentication service so it can reactively show/hide the main navigation bar when the user logs in/out of the application. I didn't worry about unsubscribing from the observable here because it's the root component of the application, the only time the component will be destroyed is when the application is closed which would destroy any subscriptions as well.
The app component contains a
logout()
method which is called from the logout link in the main nav bar above to log the user out and redirect them to the login page. The isAdmin()
getter returns true if the logged in user is in the Admin
role, or false for non-admin users.import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from './_services';
import { User, Role } from './_models';
@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
currentUser: User;
constructor(
private router: Router,
private authenticationService: AuthenticationService
) {
this.authenticationService.currentUser.subscribe(x => this.currentUser = x);
}
get isAdmin() {
return this.currentUser && this.currentUser.role === Role.Admin;
}
logout() {
this.authenticationService.logout();
this.router.navigate(['/login']);
}
}
Angular 7 App Module
Path: /src/app/app.module.ts
The app module defines the root module of the application along with metadata about the module. For more info about angular modules check out this page on the official docs site.
This is where the fake backend provider is added to the application, to switch to a real backend simply remove the providers located below the comment
// provider used to create fake backend
.import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
// used to create fake backend
import { fakeBackendProvider } from './_helpers';
import { AppComponent } from './app.component';
import { routing } from './app.routing';
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { HomeComponent } from './home';
import { AdminComponent } from './admin';
import { LoginComponent } from './login';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
HttpClientModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
AdminComponent,
LoginComponent
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
// provider used to create fake backend
fakeBackendProvider
],
bootstrap: [AppComponent]
})
export class AppModule { }
Angular 7 App Routing
Path: /src/app/app.routing.ts
The app routing file defines the routes of the application, each route contains a path and associated component. The home and admin routes are secured by passing the AuthGuard to the
canActivate
property of the route. The admin route also sets the roles
data property to [Role.Admin]
so only admin users can access it.import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home';
import { AdminComponent } from './admin';
import { LoginComponent } from './login';
import { AuthGuard } from './_guards';
import { Role } from './_models';
const appRoutes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AuthGuard]
},
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
data: { roles: [Role.Admin] }
},
{
path: 'login',
component: LoginComponent
},
// otherwise redirect to home
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(appRoutes);
Angular 7 Main Index Html File
Path: /src/index.html
The main index.html file is the initial page loaded by the browser that kicks everything off. Webpack bundles all of the javascript files together and injects them into the body of the index.html page so the scripts get loaded and executed by the browser.
<!DOCTYPE html>
<html>
<head>
<base href="/" />
<title>Angular 7 - Role Based Authorization Tutorial & Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- bootstrap css -->
<link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />
<style>
a { cursor: pointer }
</style>
</head>
<body>
<app>Loading...</app>
</body>
</html>
Angular 7 Main (Bootstrap) File
Path: /src/main.ts
The main file is the entry point used by angular to launch and bootstrap the application.
import './polyfills';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Angular 7 Polyfills
Path: /src/polyfills.ts
Some features used by Angular 7 are not yet supported natively by all major browsers, polyfills are used to add support for features where necessary so your Angular 7 application works across all major browsers.
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
Angular 7 Custom Typings File
Path: /src/typings.d.ts
A custom typings file is used to declare types that are created outside of your angular application, so the TypeScript compiler is aware of them and doesn't give you errors about unknown types. This typings file contains a declaration for the global
config
object that is created by webpack (see webpack.config.js below).// so the typescript compiler doesn't complain about the global config object
declare var config: any;
npm package.json
Path: /package.json
The package.json file contains project configuration information including package dependencies which get installed when you run
npm install
. Full documentation is available on the npm docs website.{
"name": "angular-7-role-based-authorization-example",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/cornflourblue/angular-7-role-based-authorization-example.git"
},
"scripts": {
"build": "webpack --mode production",
"start": "webpack-dev-server --mode development --open"
},
"license": "MIT",
"dependencies": {
"@angular/common": "^7.1.0",
"@angular/compiler": "^7.1.0",
"@angular/core": "^7.1.0",
"@angular/forms": "^7.1.0",
"@angular/platform-browser": "^7.1.0",
"@angular/platform-browser-dynamic": "^7.1.0",
"@angular/router": "^7.1.0",
"core-js": "^2.5.7",
"rxjs": "^6.3.3",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@types/node": "^10.12.2",
"angular2-template-loader": "^0.6.2",
"html-webpack-plugin": "^3.2.0",
"raw-loader": "^0.5.1",
"ts-loader": "^5.2.2",
"typescript": "^3.1.3",
"webpack": "^4.24.0",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
TypeScript tsconfig.json
Path: /tsconfig.json
The tsconfig.json file configures how the TypeScript compiler will convert TypeScript into JavaScript that is understood by the browser. More information is available on the TypeScript docs.
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2015",
"dom"
],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"sourceMap": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"baseUrl": "src",
"paths": {
"@/*": [
"app/*"
]
}
}
}
Webpack 4 Config
Path: /webpack.config.js
Webpack 4 is used to compile and bundle all the project files so they're ready to be loaded into a browser, it does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about webpack check out the webpack docs.
This is a minimal webpack.config.js for bundling an Angular 7 application, it compiles TypeScript files using
ts-loader
, loads angular templates with raw-loader
, and injects the bundled scripts into the body of the index.html page using the HtmlWebpackPlugin
. It also defines a global config object with the plugin webpack.DefinePlugin
.
A path alias '@' is configured in the webpack.config.js and the tsconfig.json that maps to the '/src/app' directory. This allows imports to be relative to the '/src/app' folder by prefixing the import path with '@', removing the need to use long relative paths like
import MyComponent from '../../../MyComponent'
.const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: './src/main.ts',
module: {
rules: [
{
test: /\.ts$/,
use: ['ts-loader', 'angular2-template-loader'],
exclude: /node_modules/
},
{
test: /\.(html|css)$/,
loader: 'raw-loader'
},
]
},
resolve: {
extensions: ['.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src/app/'),
}
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
inject: 'body'
}),
new webpack.DefinePlugin({
// global app config object
config: JSON.stringify({
apiUrl: 'http://localhost:4000'
})
})
],
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: true
},
devServer: {
historyApiFallback: true
}
};
Web Development Sydney
Feel free to contact me if you're looking for a web developer in Sydney, I also provide remote contracting services for clients outside Sydney.
No comments:
Post a Comment