Introduction
In this blog post, we will dive deep into using BehaviorSubject for managing authentication states in an Angular application. We will walk through a real-time example of implementing authentication, covering the setup, the authentication service, and how different components can react to authentication state changes.
If you’re new to Angular 17 and standalone components, be sure to check out our blog post on creating a simple CRUD app using the latest syntax!
What is BehaviorSubject?
BehaviorSubject is a type of Subject from RxJS (Reactive Extensions for JavaScript). Unlike a regular Subject, BehaviorSubject requires an initial value and emits the current value to new subscribers immediately upon subscription. This makes it particularly useful for representing values that change over time, such as authentication states.
Why Use BehaviorSubject for Authentication?
- Immediate Value Emission: New subscribers receive the current authentication state immediately, ensuring that components always have the latest information.
- State Sharing: Multiple parts of the application can react to authentication state changes in real-time.
- Reactive Programming: It leverages the power of RxJS to handle asynchronous data streams effectively.
Step-by-Step Implementation:
Step 1: Setting Up the Angular Project
First, create a new Angular project if you don’t have one already:
ng new auth-demo
Select css as stylesheet template. Press N for Server Side Rendering.
cd auth-demo
npm install bootstrap
We have installed bootstrap for designing our UI. Open project in VS Code or any other code editor of your choice.
Step 2: Configuring Bootstrap in Angular
Add Bootstrap’s CSS to your angular.json file so that it gets included in your project.
Open angular.json and add the Bootstrap CSS file to the styles array:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],
Step 3: Creating the Authentication Service
ng generate service services/auth
This will create a new file auth.service.ts inside the services folder.
Edit auth.service.ts file:
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private isAuthenticatedSubject: BehaviorSubject<boolean> =
new BehaviorSubject<boolean>(false);
public isAuthenticated$: Observable<boolean> =
this.isAuthenticatedSubject.asObservable();
router = inject(Router);
constructor() {
const token = localStorage.getItem('authToken');
const isAuthenticated = !!token;
this.isAuthenticatedSubject = new BehaviorSubject<boolean>(isAuthenticated);
this.isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
}
login(username: string, password: string): boolean {
if (username === 'admin' && password === 'admin') {
const token = 'dummy-token'; // Replace with actual token from server
localStorage.setItem('authToken', token); // Store the token
this.isAuthenticatedSubject.next(true); // Update state to authenticated
return true; // Login successful
} else {
return false; // Login failed
}
}
logout(): void {
this.isAuthenticatedSubject.next(false); // Update state to not authenticated
localStorage.removeItem('authToken'); // Remove the token
this.router.navigate(['/login']); // Redirect to login after logout
}
isAuthenticated(): boolean {
return this.isAuthenticatedSubject.value; // Synchronously check authentication state
}
}
Explanation:
This AuthService is an Angular service that manages user authentication state using BehaviorSubject and localStorage. When the service is initialized, it checks for a token in localStorage. If a token is found, it updates the authentication state to true, otherwise, it sets it to false.
The login method validates the user’s credentials (in this case, simply checking if the username and password are both ‘admin’). If the credentials are valid, a dummy token is stored in localStorage, and the authentication state is updated to true. The logout method clears the token from localStorage, updates the authentication state to false, and redirects the user to the login page.
The isAuthenticated method synchronously checks and returns the current authentication state.
In a real-world scenario, you would replace the dummy token with an actual JWT obtained from the server upon successful login. This token should be sent with each request to the server, where it will be validated to ensure the user’s authentication status. This example provides a basic understanding of the mechanism without implementing server-side validation and JWT management.
Step 4: Creating the Login Component
Create a component for logging in. This component will use the AuthService to log in the user and will react to the authentication state changes.
ng generate component login
Edit login.component.ts file:
import { Component, inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-login',
standalone: true,
imports: [FormsModule],
templateUrl: './login.component.html',
styleUrl: './login.component.css',
})
export class LoginComponent {
authService = inject(AuthService);
router = inject(Router);
username: string = '';
password: string = '';
onLogin(): void {
if (this.authService.login(this.username, this.password)) {
this.router.navigate(['/home']); // Redirect to home if login is successful
} else {
alert('Login failed. Please check your credentials.'); // Show an error message if login fails
}
}
}
Edit login.component.html file:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4>Login</h4>
</div>
<div class="card-body">
<form (ngSubmit)="onLogin()">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
class="form-control"
name="username"
[(ngModel)]="username"
/>
</div>
<div class="form-group mt-3">
<label for="password">Password</label>
<input
type="password"
class="form-control"
name="password"
[(ngModel)]="password"
/>
</div>
<button type="submit" class="btn btn-primary mt-4">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>
Explanation:
This LoginComponent is an Angular 17 standalone component designed to handle user login. It uses the new imports array to directly include FormsModule, making the component more modular and flexible.
TypeScript part:
- Injects AuthService and Router using Angular’s inject function.
- Defines username and password properties bound to input fields.
- The onLogin method calls AuthService’s login method to check credentials. Successful login redirects to /home; failed login shows an alert.
HTML part:
- Contains a form with ngSubmit directive bound to onLogin method.
- Uses two-way data binding ([(ngModel)]) for username and password input fields.
- Provides a styled form using Bootstrap classes.
Step 5: Creating the Header Component
Create a HeaderComponent that subscribes to isAuthenticated$ and updates the UI based on the authentication state.
ng generate component header
Edit header.component.ts file:
import { Component, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-header',
standalone: true,
imports: [CommonModule],
templateUrl: './header.component.html',
styleUrl: './header.component.css',
})
export class HeaderComponent {
isAuthenticated!: Observable<boolean>;
authService = inject(AuthService);
ngOnInit() {
this.isAuthenticated = this.authService.isAuthenticated$;
}
logout(): void {
this.authService.logout();
}
}
Edit home.component.html file:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Auth Demo</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ml-auto">
@if (!(isAuthenticated | async)) {
<li class="nav-item">
<a class="nav-link" routerLink="/login">Login</a>
</li>
} @else {
<li class="nav-item">
<a class="nav-link" (click)="logout()">Logout</a>
</li>
}
</ul>
</div>
</nav>
Explanation:
The HeaderComponent is an Angular 17 standalone component that displays a navigation bar with login and logout options based on the user’s authentication state.
Imports Array: The imports array in HeaderComponent includes CommonModule, which is essential for common Angular functionalities. Even though the new Angular 17 syntax @if and @else is used for conditionals, CommonModule is still required for other common directives and pipes that might be needed in the component.
Dependency Injection: Uses Angular’s inject function to inject AuthService.
Authentication State: Subscribes to isAuthenticated$ observable from AuthService in ngOnInit.
Logout: Provides a logout method to log out the user.
HTML Template:
Displays “Login” or “Logout” links using the new Angular 17 @if and @else syntax based on the isAuthenticated observable.
This setup provides a dynamic navigation bar that updates based on the user’s login status.
Step 6: Creating Home Component
Create a component for the home page, which will be accessible only to authenticated users.
ng generate component home
Edit home.component.ts file:
import { Component } from '@angular/core';
import { HeaderComponent } from '../header/header.component';
@Component({
selector: 'app-home',
standalone: true,
imports: [HeaderComponent],
templateUrl: './home.component.html',
styleUrl: './home.component.css',
})
export class HomeComponent {}
Edit home.component.html file:
<app-header></app-header>
<div class="container mt-5">
<h1>Welcome to the Home Page</h1>
<p>You are logged in.</p>
</div>
Explanation: The HomeComponent serves as the landing page for authenticated users, displaying a welcome message. Here in the imports array we have imported the HeaderComponent, so that we can use the HeaderComponent in the HomeComponent.
Step 7: Protecting Routes with Authentication Guard
Create a guard to protect certain routes based on the authentication state.
ng generate guard guards/auth
Choose canActivate as the type of guard.
Edit auth.guard.ts file:
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
} else {
router.navigate(['/login']);
return false;
}
};
Explanation:
This code defines an authGuard function to protect routes in an Angular application. It uses Angular’s inject function to access AuthService and Router. The guard checks if the user is authenticated using AuthService. If authenticated, it allows route activation (return true); otherwise, it redirects the user to the login page (router.navigate([‘/login’])) and prevents route activation (return false).
Step 8: Configure Routing
Update the app.routes.ts file to include the login and home routes, and protect the home route using the AuthGuard.
import { Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { HomeComponent } from './home/home.component';
import { authGuard } from './guards/auth.guard';
export const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent, canActivate: [authGuard] },
{ path: '', redirectTo: '/login', pathMatch: 'full' },
];
Explanation:
This code defines the routing configuration for an Angular application:
- login route displays the LoginComponent.
- home route displays the HomeComponent and is protected by authGuard from Step 7.
- The default route (”) redirects to the login route.
Step 9: Update App Component
Edit app.component.html file:
<router-outlet></router-outlet>
Explanation:
The <router-outlet> directive in app.component.html acts as a placeholder for rendering the routed components based on the current route.
Step 10: Run the App
ng serve -o
Conclusion
Using BehaviorSubject in Angular for managing authentication states provides a robust and reactive way to handle user authentication. It ensures that all parts of the application are always aware of the current authentication state, enabling a seamless and secure user experience. By following the steps outlined in this blog post and using Bootstrap for styling, you can implement a real-time authentication system in your Angular applications, leveraging the power of RxJS and Angular’s dependency injection system.