To start a new angular project we will use the angular cli. If we do not have in installed we just use:
We will need to install bootstrap because we will use it in our application, so we will install it using
npm install --save bootstrap
also, inside the
styles.css file we will add the line:
@import "~bootstrap/dist/css/bootstrap.css";
We will asume that we have our backend running on localhost:3000.
Authentification services
We will start creating the angular services that will communicate with the backend to login and register a new user
we will create the following services and files using "ng g service (...)"
Let us see how they work
auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { LoginData, UserRetrieved, NewUser } from './user';
import { Subject } from 'rxjs';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private http: HttpClient, private router: Router) { }
urlBackend = 'http://localhost:3000';
loggedUser: string;
loggedUser$: Subject<string>; = new Subject();
async login(loginData: LoginData) {
const data: any = await this.http.post(this.urlBackend + '/security/login', loginData)
.toPromise();
localStorage.setItem('jwtToken', data.token);
this.loggedUser = loginData.username;
this.loggedUser$.next(this.loggedUser);
}
async signup(signupData: NewUser) {
return await this.http.post(this.urlBackend + '/security/signup', signupData).toPromise();
}
async logout() {
this.loggedUser = null;
this.loggedUser$.next(this.loggedUser);
this.router.navigate(['/']);
}
}
As you can see, we are using the angular http module to access the login endpoint and storage the token. We are saving the token in the local storage when we do this:
...
localStorage.setItem('jwtToken', data.token);
...
To sign up we are are posting the new user data in the right route too.
We are using the observable
loggedUser$ to publish when the user has changed.
We also add an auth guard, that will be used to redirect the user to another route if the user is not authorized:
auth-guard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (!this.authService.loggedUser) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
we add the typescript file user.ts to create various interfaces to pass the user data:
export interface LoginData {
username: string;
password: string;
}
export interface UserRetrieved {
username: string;
token: string;
}
export interface NewUser {
password: string;
username: string;
email: string;
}
Router module and routes
We will need a router module to configure the routes in our app. We will add routes for login, signup and entries. We will also need to add the AuthGuard class in the providers, so that it redirects the user to home in case the user is not authenticated.
app-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Route } from '@angular/compiler/src/core';
import { LoginComponent } from './login/login.component';
import { SingupComponent } from './singup/singup.component';
import { NewEntryComponent } from './new-entry/new-entry.component';
import { RouterModule } from '@angular/router';
import { UserPageComponent } from './user-page/user-page.component';
import { AuthGuard } from './auth/auth-guard.service';
import { EntriesComponent } from './entries/entries.component';
const routes = [
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
{
path: 'login',
component: LoginComponent
},
{
path: 'signup',
component: SingupComponent
},
{
path: 'new_entry',
component: NewEntryComponent,
canActivate: [
AuthGuard
]
},
{
path: 'entries',
component: EntriesComponent,
},
{
path: 'user_page',
component: UserPageComponent,
canActivate: [
AuthGuard
]
}
];
@NgModule({
imports: [
CommonModule, RouterModule.forRoot(routes)
],
exports: [RouterModule],
providers: [AuthGuard],
declarations: []
})
export class AppRoutingModule { }
Blog entries service
We will invoke the backend and retrieve the blog data using the following EntriesService:
entries.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class EntriesService {
constructor(private http: HttpClient) { }
urlBackend = 'http://localhost:3000';
async getEntry(title: string): Promise<Object> {
const httpOptions = {
headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
};
return await this.http.get(this.urlBackend + '/titled/' + title, httpOptions)
.toPromise();
}
async getEntries(limit: number, skip: number): Promise<any> {
return await this.http.get(this.urlBackend + '/entries/entries_list/limit=' +
limit + '&skip=' + skip).toPromise();
}
async saveEntry(entry: BlogEntry): Promise<Object> {
const httpOptions = {
headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
};
return await this.http.post(this.urlBackend + '/entries/save_entry', entry,
httpOptions).toPromise();
}
}
export interface BlogEntry {
title: string;
content: string;
tags: string;
}
Notice that we
are adding a token into the http header when we do:
...
const httpOptions = {
headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
};
...
this token will is storaged in localStorage, and is saved there when the user logs in (we did this in
auth.service.ts)
Login and Signup Components
We will create a component to log in into the application
login.component.ts
import { Component, OnInit } from '@angular/core';
// import { Router } from "@angular/router";
import { Observable } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { LoginData } from '../auth/user';
import { AuthService } from '../auth/auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor(private router: Router, private authService: AuthService) { }
loginData: LoginData = { username: '', password: '' };
message = '';
data: any;
ngOnInit() {
}
async login() {
try {
await this.authService.login(this.loginData);
this.router.navigate(['user_page']);
this.message = '';
} catch (err) {
this.message = err.error.msg;
}
}
}
login.component.html
<div class="form-signin custom-login">
<div class="alert alert-warning alert-dismissible" role="alert"
*ngIf="message !== ''">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close"><span aria-hidden="true">�-</span></button>
{{message}}
</div>
<h2 class="form-signin-heading">Log in</h2>
<label for="inputUsername" class="sr-only">Email address</label>
<input type="text" class="form-control"
placeholder="username" [(ngModel)]="loginData.username" name="username"
required />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" class="form-control" placeholder="Password"
[(ngModel)]="loginData.password" name="password"
required />
<button class="btn btn-lg btn-primary btn-block"
(click)="login()">Log in</button>
<div>
<a routerLink='/signup'>sign up</a>
</div>
</div>
When the user clicks log in, we invoke the login service and pass the user and password. If the operation is successful we navigate to user_page
This is how it will look like:
Now let us see how to register new users:
singup.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Router } from '@angular/router';
import { NewUser } from '../auth/user';
@Component({
selector: 'app-singup',
templateUrl: './singup.component.html',
styleUrls: ['./singup.component.css']
})
export class SingupComponent implements OnInit {
constructor(private router: Router, private authService: AuthService) { }
newUser: NewUser = { username: '', password: '', email: '' };
message = '';
ngOnInit() {
}
async signup() {
try {
await this.authService.signup(this.newUser);
this.router.navigate(['/user_page']);
} catch (err) {
this.message = err.error.msg;
}
}
}
signup.component.html
<div class="form-signin custom-signup">
<div class="alert alert-warning alert-dismissible" role="alert" *ngIf="message !== ''">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span></button>
{{message}}
</div>
<h2 class="form-signin-heading">Create an account</h2>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" class="form-control" placeholder="Email address"
[(ngModel)]="newUser.email" name="email" />
<input type="text" class="form-control" placeholder="User name"
[(ngModel)]="newUser.username" name="username"
required />
<label for="inputPassword" class="sr-only">Choose a password</label>
<input type="password" class="form-control" placeholder="Password"
[(ngModel)]="newUser.password" name="password"
required />
<button class="btn btn-lg btn-primary btn-block" (click)="signup()">Sign Up</button>
<div>
<a routerLink='/login'>login instead</a>
</div>
</div>
This is how it looks like:
The behaviour is quite simple, we just pass the data to the service and redirect to another page if everything goes ok.
navbar component
Let us take a look into the navigation bar
import { Component, OnInit, OnDestroy } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit, OnDestroy {
loggedUser: string;
loggedUserSubs: Subscription;
constructor(private authService: AuthService) { }
ngOnInit() {
this.loggedUser = this.authService.loggedUser;
this.loggedUserSubs = this.authService.loggedUser$.subscribe((loggedUser) => {
this.loggedUser = loggedUser;
});
}
ngOnDestroy() {
this.loggedUserSubs.unsubscribe();
}
logout() {
this.authService.logout();
}
}
When the component is created we subscribe to the observable
loggedUser from the service
authService that we have injected
. Doing this we update the navbar if the user logs in. This will allow us to show the current user in the navigation bar.
The navbar html is built with bootstrap:
<nav class="navbar navbar-dark bg-dark mb-5">
<a class="navbar-brand" href="/">Secured App</a>
<div class="navbar-expand mr-auto">
<div class="navbar-nav">
<a class="nav-item nav-link" routerLink="/entries"
routerLinkActive="active">Entries</a>
<a class="nav-item nav-link" routerLink="/new_entry"
routerLinkActive="active">New entry</a>
<a class="nav-item nav-link" routerLink="/user_page"
routerLinkActive="active">user page</a>
</div>
</div>
<div class="navbar-expand ml-auto navbar-nav">
<div class="navbar-nav">
<span *ngIf="loggedUser" class="nav-item nav-link">
{{loggedUser}}
</span>
<a *ngIf="loggedUser" class="nav-item nav-link" (click)="logout()">
logout
</a>
<a *ngIf="!loggedUser" class="nav-item nav-link" routerLink="/login"
routerLinkActive="active">
login
</a>
<a class="nav-item nav-link" routerLink="/signup" routerLinkActive="active">
signup
</a>
</div>
</div>
</nav>
This is our navbar:
When the user logs in it will appear in the left corner:
new-entry component
We will also have a component to publish new posts. This component will use our entries service to send data to the back end.
new-entry.component.ts
import { Component, OnInit } from '@angular/core';
import { BlogEntry, EntriesService } from '../entries.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-new-entry',
templateUrl: './new-entry.component.html',
styleUrls: ['./new-entry.component.css']
})
export class NewEntryComponent implements OnInit {
constructor(private router: Router, private entriesService: EntriesService) { }
entry: BlogEntry = { title: 'enter title', content: 'enter content', tags: 'enter tags' };
ngOnInit() {
}
async submmit() {
await this.entriesService.saveEntry(this.entry);
this.router.navigate(['entries']);
}
}
new-entry.component.ts
<label for="title" class="sr-only">Title</label>
<input type="text" class="form-control" placeholder="title" [(ngModel)]="entry.title"
name="title" required />
<label for="tags" class="sr-only">Tags</label>
<input type="text" class="form-control" placeholder="tags" [(ngModel)]="entry.tags"
name="tags" required />
<div class="form-group">
<label for="content">Content</label>
<textarea class="form-control rounded-0" id="content" rows="10"
[(ngModel)]="entry.content"></textarea>
</div>
<button class="btn btn-primary" (click)="submmit()">Send</button>
when the user clicks the button we send the two text fields in the form of a BlogEntry to the entriesService. Then the entries service sends the data adding the token into the heading.
This is how it looks like:
entries component
entries.component.ts
import { Component, OnInit } from '@angular/core';
import { EntriesService, BlogEntry } from '../entries.service';
@Component({
selector: 'app-entries',
templateUrl: './entries.component.html',
styleUrls: ['./entries.component.css']
})
export class EntriesComponent implements OnInit {
constructor(private entriesService: EntriesService) { }
entries: BlogEntry[];
async ngOnInit() {
this.entries = await this.entriesService.getEntries(10, 0);
console.log(this.entries);
}
}
entries.component.html
<h2>Entries</h2>
<div class="card" *ngFor="let entry of entries">
<div class="card-title"><strong>{{entry.title}}</strong></div>
<div class="card-body">{{entry.content}}</div>
</div>
This component just retrieves all the entries using entriesService. This does not require authentification.
This is how it looks:
Download the code: