Horje
Multi Factor authentication using MEAN

Multi-factor authentication is important and common in every website or app to securely login the user. In this article, we will see how we can implement Multi-factor authentication using MEAN Stack. MEAN Stack includes the use of Angular for frontend, Node JS and Express for backend, and MongoDB as the database.

Project Preview:

Mean Multidactor authentication preview

PROJECT PREVIEW IMAGE

Prerequisites:

Approach to Create Address Book using MEAN Stack

Backend:

  • Set up a new node js project
  • Set up the server using express with CORS as the middleware in file server.js
  • Create the app instance using const app = express()
  • Twilio a cloud communication platform used to send or receive text message which can help developers to implement the multi-factor authentication functionality.
  • Set up a twilio account and get the configuration details
  • Create controllers folder which will define the API methods
  • Create models folder to create the database schema for user
  • Create router folder and mention all the routes related to user login, register and verify for checking the code entered by user
  • Set up local Mongo DB database.
  • Set up the connection to local Mongo DB in server.js file
  • Create collections within the database to store and retrieve the data from database
  • One collection is created – user
  • Create .env files for storing the environment variables for config details related to twilio account, database and port
  • Implement the core logic of register, login and generation and verification of the code
  • Test the API endpoints using postman

Frontend:

  • Create a new angular project
  • Create components folder and seperate components for seperate routes – user component for login and register functionality and verify component for verification code feature
  • Create HTML, CSS and ts files for all the components
  • Create service to establish communication between frontend and backend routes
  • Create various routes in app.routes.ts folder for login, register and verify
  • Test the frontend application in browser at https://localhost:3000

Steps to Create the Application

Step 1: Create the main folder for complete project

mkdir multi-factor-auth
cd multi-factor-auth

Step 2: Initialize the node.js project

npm init -y

Step 3: Install the required dependencies

npm install express mongoose jsonwebtoken bcryptjs nodemon twilio cors

Project Structure (Backend):

Screenshot-2024-04-16-003525

PROJECT STRUCTURE FOR BACKEND

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.3.1",
"twilio": "^5.0.3"
}

Example: Create the required files as seen on the project structure and add the following codes.

Node
// authController.js

const User = require("../model/authModel");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const twilio = require("twilio");
require("dotenv").config({ path: __dirname + "/config/.env" });
const twilioClient = twilio(
    process.env.TWILIO_ACCOUNT_SID,
    process.env.TWILIO_AUTH_TOKEN
);
exports.register = async (req, res) => {
    try {
        const { username, email, password, phone } = req.body;
        let user = await User.findOne({ email });
        if (user) {
            return res.status(400).json({
                message: "User Already Exist",
                success: false,
            });
        }
        user = new User({
            username: username,
            email: email,
            password: password,
            phone: phone,
        });
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(password, salt);
        await user.save();
        const token = generateJwtToken(user._id);
        res.status(201).json({
            success: true,
            token: token,
            message: "User registered successfully",
        });
    } catch (error) {
        res.status(500).json({
            message: "Server error! New user registration failed",
            success: false,
        });
    }
};

exports.login = async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });
        if (!user) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false,
            });
        }
        const isMatched = await bcrypt.compare(password, user.password);
        if (!isMatched) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false,
            });
        }
        const verificationCode = Math.floor(100000 + Math.random() * 900000);
        const codeExpires = new Date();
        codeExpires.setMinutes(codeExpires.getMinutes() + 10);
        user.loginCode = verificationCode.toString();
        user.codeExpires = codeExpires;
        await user.save();

        await twilioClient.messages.create({
            body: `Your login verification code is ${verificationCode}`,
            from: process.env.TWILIO_PHONE_NUMBER,
            to: `+91` + user.phone,
        });
        return res.status(200).json({
            message: "Verification code sent to your phone.",
            userId: user._id,
        });
    } catch (error) {
        return res.status(500).json({
            success: false,
            message: "Internal Server Error, Login unsuccessful",
        });
    }
};

function generateJwtToken(userID) {
    const payload = {
        user: {
            id: userID,
        },
    };
    return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: 3600 });
}

exports.verifyUser = async (req, res) => {
    const { userId, code } = req.body;
    console.log(userId);
    try {
        const user = await User.findById(userId);
        if (!user) {
            return res.status(404).send("User not found.");
        }
        const currentTime = new Date();
        if (currentTime > user.codeExpires) {
            return res
                .status(400)
                .send(
                    "Verification code has expired. Please login again to receive a new code."
                );
        }
        if (user.loginCode !== code) {
            return res.status(400).send("Invalid verification code.");
        }

        user.verified = true;
        user.loginCode = null;
        user.codeExpires = null;
        await user.save();
        const token = generateJwtToken(user._id);
        res.status(200).json({
            message: "User successfully verified.",
            token: token,
        });
    } catch (error) {
        res.status(500).json("Verification error: " + error.message);
    }
};
Node
// authModel.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        uniques: true
    },
    password: {
        type: String,
        required: true
    },
    phone: {
        type: Number,
        required: true
    },
    verified: {
        type: Boolean,
        default: false,
        required: false
    },
    loginCode: {
        type: String,
        default: "",
        required: false
    },
    codeExpires: {
        type: Date,
        required: false
    }
});

module.exports = mongoose.model('User', userSchema);
Node
// authRoutes.js

const express = require('express');
const router = express.Router();
const authController = require('../controller/authController');

router.post('/register', authController.register);

router.post('/login', authController.login);

router.post('/verify', authController.verifyUser);

module.exports = router;
Node
// server.js

const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const authRoutes = require("../backend/route/authRoute");
const dotenv = require("dotenv");
dotenv.config({ path: "../backend/config/.env" });
const app = express();
app.use(cors());
app.use(express.json());

mongoose
    .connect(process.env.DB_URI, {
        family: 4,
    })
    .then(() => console.log("Mongo DB connected"))
    .catch((err) => console.log(err));

app.use("/api/auth", authRoutes);

const PORT = process.env.PORT;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Node
// .env file

PORT=5000
DB_URI=mongodb://localhost:27017/multi-factor-auth
JWT_SECRET=jwtSecret
TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID
TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN
TWILIO_PHONE_NUMBER=YOUR_TRIAL_TWILIO_PHONE_NUMBER

To start the backend run the following command.

nodemon server.js

Step 5: Install the angular CLI

npm install -g @angular/cli

Step 6: Create a new angular project

ng new frontend

Step 7: Create components for different functionalities in angular

Syntax - ng generate component <component-name>
ng generate component user
ng generate component verify

Step 8: Create services for communication between frontend and backend

Syntax - ng generate service <service-name>
ng generate service user
ng generate service shared

Project Structure (Frontend):

projectStructureFrontend

PROJECT STRUCTURE FOR FRONTEND

The updated dependencies in package.json file of frontend will look like:

"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
"@angular/core": "^17.2.0",
"@angular/forms": "^17.2.0",
"@angular/platform-browser": "^17.2.0",
"@angular/platform-browser-dynamic": "^17.2.0",
"@angular/platform-server": "^17.2.0",
"@angular/router": "^17.2.0",
"@angular/ssr": "^17.2.3",
"@auth0/angular-jwt": "^5.2.0",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}

Example: Create the required files as seen in project structure and add the following codes.

HTML
<!-- user.component.html -->

<div class="error-message" *ngIf="errorMessage">{{ errorMessage }}</div>
<div class="success-message" *ngIf="successMessage">{{ successMessage }}</div>

<div class="container" *ngIf="loginActive">
  <h2>Login</h2>
  <form (ngSubmit)="login()">
    <div class="form-group">
      <label for="email">Email:</label>
      <input
        type="email"
        class="form-control"
        id="email"
        name="email"
        [(ngModel)]="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password:</label>
      <input
        type="password"
        class="form-control"
        id="password"
        name="password"
        [(ngModel)]="password"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary">Login</button>
  </form>
</div>
<div class="container" *ngIf="registerActive">
  <h2>Register</h2>
  <form (submit)="register()">
    <div class="form-group">
      <label for="username">Username</label>
      <input
        type="text"
        id="username"
        class="form-control"
        [(ngModel)]="username"
        name="username"
        required
      />
    </div>
    <div class="form-group">
      <label for="email">Email</label>
      <input
        type="email"
        id="email"
        class="form-control"
        [(ngModel)]="email"
        name="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input
        type="password"
        id="password"
        class="form-control"
        [(ngModel)]="password"
        name="password"
        required
      />
    </div>
    <div class="form-group">
      <label for="phone">Phone Number</label>
      <input
        type="number"
        id="phone"
        class="form-control"
        [(ngModel)]="phone"
        name="phone"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
  </form>
</div>
HTML
<!-- verify.component.html -->

<div class="verify-container">
    <h3>Please Enter your Verification Code</h3>
    <form (ngSubmit)="onVerify()">
        <div>
            <label for="code">Verification Code: </label>
            <input type="text" id="code" [(ngModel)]="code" name="code" required>
        </div>
        <button type="submit">Verify</button>
        <p *ngIf="errorMessage">{{ errorMessage }}</p>
    </form>
</div>
HTML
<!-- dashboard.component.html -->

<div class="verify-container">
    <h3>Please Enter your Verification Code</h3>
    <form (ngSubmit)="onVerify()">
        <div>
            <label for="code">Verification Code: </label>
            <input type="text" id="code" [(ngModel)]="code" name="code" required>
        </div>
        <button type="submit">Verify</button>
        <p *ngIf="errorMessage">{{ errorMessage }}</p>
    </form>
</div>
HTML
<!-- app.component.html -->

<main class="main">
    <div class="content">
        <div class="left-side">
            <h1>{{ title }}</h1>
            <div>
                <ul>
                    <li><a (click)="login()" *ngIf="!isLoggedIn">Login</a></li>
                    <li><a (click)="register()" *ngIf="!isLoggedIn">Register</a></li>
                    <li><a (click)="logout()" *ngIf="isLoggedIn">Logout</a></li>
                </ul>
            </div>
        </div>

    </div>
</main>
<router-outlet>
</router-outlet>
CSS
/* user.component.css */

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 2.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"] {
    width: 97%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 3.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="email"],
input[type="password"] {
    width: 99%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.error-message {
    color: #FF0000;
    background-color: #FFEFEF;
    padding: 10px;
    border: 1px solid #FF0000;
    border-radius: 5px;
    margin-bottom: 10px;
    margin-top: 10px;
}

.success-message {
    color: green;
    background-color: rgb(186, 218, 186);
    padding: 10px;
    border: 1px solid green;
    border-radius: 5px;
    margin-bottom: 10px;
    margin-top: 10px;
}
CSS
/* verify.component.css */

.verify-container {
    width: 100%;
    max-width: 400px;
    margin: 50px auto;
    padding: 20px;
    background-color: #f8f9fa;
    border: 1px solid #ced4da;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.verify-container h3 {
    text-align: center;
    color: #333;
    margin-bottom: 20px;
}

.verify-container form {
    display: flex;
    flex-direction: column;
}

.verify-container label {
    font-size: 16px;
    color: #495057;
    margin-bottom: 5px;
}

.verify-container input[type="text"] {
    height: 38px;
    width: 63%;
    padding: 6px 12px;
    font-size: 16px;
    color: #495057;
    background-color: #fff;
    border: 1px solid #ced4da;
    border-radius: 4px;
    margin-bottom: 20px;
}

.verify-container button {
    background-color: #3b5775;
    color: white;
    border: none;
    padding: 10px 15px;
    font-size: 16px;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

.verify-container button:hover {
    background-color: #0056b3;
}

.verify-container p {
    color: #dc3545;
    font-size: 14px;
    margin-top: 0;
}
CSS
/* dashboard.component.css */

.dashboard-container {
    margin: 20px;
    padding: 20px;
    border: 1px solid green;
    border-radius: 5px;
    text-align: center;
    color: green;
    background-color: rgb(164, 220, 164);
}
CSS
/* app.component.css */

.main {
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: darkslategrey;
    color: white;
}

.content {
    width: 100%;
    max-width: 1200px;
    padding: 20px;
}

.left-side {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.left-side h1 {
    margin: 0;
    margin-left: 3%;
}

.left-side ul {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

.left-side li {
    display: inline;
    margin-right: 20px;
}

.left-side li a {
    text-decoration: none;
    color: white;
    font-weight: bold;
    font-size: 1.5rem;
}

.left-side li a:hover {
    color: lightgray;
}

a {
    cursor: pointer;
}
JavaScript
// user.component.ts

import { Component, OnInit } from "@angular/core";
import { UserService } from "../../services/user.service";
import { Router } from "@angular/router";
import { SharedService } from "../../services/shared.service";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

@Component({
    selector: "app-user",
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: "./user.component.html",
    styleUrl: "./user.component.css",
})
export class UserComponent implements OnInit {
    username!: string;
    email!: string;
    password!: string;
    phone: number = 0;
    credentials: any = {};
    successMessage: string = "";
    errorMessage: string = "";
    loginActive: boolean = true;
    registerActive: boolean = false;
    constructor(
        private userService: UserService,
        private router: Router,
        private sharedService: SharedService
    ) { }

    ngOnInit(): void {
        this.sharedService.loginEvent.subscribe(() => {
            this.loginActive = true;
            this.registerActive = false;
            this.username = "";
            this.email = "";
            this.password = "";
            this.successMessage = "";
            this.errorMessage = "";
        });
        this.sharedService.registerEvent.subscribe(() => {
            this.registerActive = true;
            this.loginActive = false;
            this.username = "";
            this.email = "";
            this.password = "";
            this.successMessage = "";
            this.errorMessage = "";
        });
    }

    login(): void {
        const credentials = {
            email: this.email,
            password: this.password,
        };
        this.userService.login(credentials).subscribe(
            (response: any) => {
                this.loginActive = false;
                this.registerActive = false;
                this.router.navigate([`/verify/${response.userId}`]);
                this.successMessage = response.message;
                this.errorMessage = "";
            },
            (error: any) => {
                console.error("Error logging in:", error);
                this.errorMessage =
                    "Login unsuccessfull ! Please reload or try in incognito tab";
                this.successMessage = "";
            }
        );
    }

    register(): void {
        const userData = {
            username: this.username,
            email: this.email,
            password: this.password,
            phone: this.phone,
        };

        this.userService.register(userData).subscribe(
            (response: any) => {
                this.successMessage = response.message;
                this.errorMessage = "";
                this.loginActive = true;
                this.registerActive = false;
            },
            (error: any) => {
                this.errorMessage = "User not registered successfully";
                this.successMessage = "";
            }
        );
    }
}
JavaScript
// verify.component.ts

import { Component, OnInit } from "@angular/core";
import { UserService } from "../../services/user.service";
import { ActivatedRoute, Router } from "@angular/router";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

@Component({
    selector: "app-verify",
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: "./verify.component.html",
    styleUrls: ["./verify.component.css"],
})
export class VerifyComponent implements OnInit {
    userId: string = "";
    code: string = "";
    errorMessage: string = "";
    successMessage: string = "";
    constructor(
        private authService: UserService,
        private router: Router,
        private route: ActivatedRoute
    ) { }
    ngOnInit() {
        this.route.params.subscribe((params: any) => {
            this.userId = params["id"];
        });
    }

    onVerify(): void {
        this.authService.verifyCode(this.userId, this.code).subscribe({
            next: (response: any) => {
                console.log("Verification successful", response);
                this.errorMessage = "";
                const token = response.token;
                localStorage.setItem("token", token);
                this.authService.setAuthenticationStatus(true);
                this.authService.emitLoggedInEvent();
                this.successMessage = response.message;
                this.router.navigate(["/dashboard"]);
            },
            error: (error: any) => {
                this.errorMessage = "Verification failed";
                console.error("Error during verification", error);
            },
        });
    }
}
JavaScript
// dashboard.component.ts

import { Component, OnInit } from "@angular/core";

@Component({
    selector: "app-dashboard",
    standalone: true,
    imports: [],
    templateUrl: "./dashboard.component.html",
    styleUrl: "./dashboard.component.css",
})
export class DashboardComponent implements OnInit {
    message: string;

    constructor() {
        this.message = "You are logged in successfully!";
    }

    ngOnInit(): void { }
}
JavaScript
// user.service.ts

import { HttpClient } from "@angular/common/http";
import { EventEmitter, Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";

@Injectable({
    providedIn: "root",
})
export class UserService {
    private baseUrl = "http://localhost:5000/api/auth";
    constructor(private httpClient: HttpClient) { }

    register(userData: any): Observable<any> {
        return this.httpClient.post(`${this.baseUrl}/register`, userData);
    }

    login(credentials: any): Observable<any> {
        return this.httpClient.post(`${this.baseUrl}/login`, credentials);
    }

    private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);

    isAuthenticated(): Observable<boolean> {
        return this.isAuthenticatedSubject.asObservable();
    }

    setAuthenticationStatus(isAuthenticated: boolean): void {
        this.isAuthenticatedSubject.next(isAuthenticated);
    }

    loggedInEvent: EventEmitter<any> = new EventEmitter();
    emitLoggedInEvent() {
        this.loggedInEvent.emit();
    }

    verifyCode(userId: string, code: string): Observable<any> {
        return this.httpClient.post(`${this.baseUrl}/verify`, { userId, code });
    }
}
JavaScript
// shared.service.ts

import { EventEmitter, Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
})
export class SharedService {
    loginEvent: EventEmitter<void> = new EventEmitter<void>();
    registerEvent: EventEmitter<void> = new EventEmitter<void>();
    constructor() { }

    triggerLoginEvent(): void {
        this.loginEvent.emit();
    }

    triggerRegisterEvent(): void {
        this.registerEvent.emit();
    }
}
JavaScript
// app.routes.ts

import { Routes } from "@angular/router";
import { UserComponent } from "./components/user/user.component";
import { VerifyComponent } from "./components/verify/verify.component";
import { DashboardComponent } from "./components/dashboard/dashboard.component";

export const routes: Routes = [
    { path: "", component: UserComponent },
    { path: "verify/:id", component: VerifyComponent },
    { path: "dashboard", component: DashboardComponent },
    { path: "**", redirectTo: "/" },
];
JavaScript
// app.module.ts

import { InjectionToken, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';
import { UserComponent } from './components/user/user.component';
import { VerifyComponent } from './components/verify/verify.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
@NgModule({
    declarations: [
        AppComponent,
        UserComponent,
        VerifyComponent,
        DashboardComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        RouterModule.forRoot(routes),
    ],
    exports: [RouterModule],
    providers: [{ provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, JwtHelperService],
    bootstrap: [AppComponent]
})
export class AppModule { }
JavaScript
// app.component.ts

import { Component } from "@angular/core";
import { Router, RouterOutlet } from "@angular/router";
import { SharedService } from "./services/shared.service";
import { UserService } from "./services/user.service";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

@Component({
    selector: "app-root",
    standalone: true,
    imports: [RouterOutlet, FormsModule, CommonModule],
    templateUrl: "./app.component.html",
    styleUrl: "./app.component.css",
})
export class AppComponent {
    title = "Multi Factor Authentication";
    isLoggedIn: boolean = false;
    constructor(
        private router: Router,
        private userService: UserService,
        private sharedService: SharedService
    ) { }
    ngOnInit(): void {
        this.userService.loggedInEvent.subscribe((data: any) => {
            this.isLoggedIn = true;
        });
        if (typeof localStorage !== "undefined" && localStorage.getItem("token")) {
            this.isLoggedIn = true;
        }
    }

    login(): void {
        this.sharedService.triggerLoginEvent();
        this.router.navigate(["/"]);
    }

    register(): void {
        this.sharedService.triggerRegisterEvent();
        this.router.navigate(["/"]);
    }

    logout(): void {
        this.userService.setAuthenticationStatus(false);
        this.isLoggedIn = false;
        localStorage.removeItem("token");
        this.router.navigate(["/"]);
    }
}

To start the application run the following command.

ng serve

Output:

MultiFactor Auth in angular

OUTPUT GIF




Reffered: https://www.geeksforgeeks.org


AngularJS

Related
Make HTTP requests in Angular? Make HTTP requests in Angular?
What are decorators in Angular? What are decorators in Angular?
What is router-outlet in Angular, and where is it used? What is router-outlet in Angular, and where is it used?
How to use services with HTTP methods in Angular v17? How to use services with HTTP methods in Angular v17?
How to make an http call in angular How to make an http call in angular

Type:
Geek
Category:
Coding
Sub Category:
Tutorial
Uploaded by:
Admin
Views:
14