Angular 20

Angular 20 and Flask CRUD Tutorial – Learn by building Insert Update Delete App

Building a CRUD Application with Angular 20 and Flask

In this tutorial, we’ll learn how to build a simple CRUD (Create, Read, Update, Delete) application using Angular 20 for the frontend and Flask for the backend. CRUD operations are fundamental to web development, allowing users to interact with data in a dynamic way. By combining Angular 20, a powerful frontend framework, with Flask, a lightweight and flexible Python-based web framework, we can create an efficient full-stack application.

Throughout this tutorial, we will cover setting up the Angular application, creating the necessary components, and integrating it with Flask for handling API requests. You’ll learn how to manage data on the client-side with Angular and perform backend operations like inserting, updating, and deleting records using Flask’s routing and database management features. By the end of this tutorial, you’ll have a working web app that demonstrates the power of combining Angular and Flask for building real-time, dynamic applications.

Project Structure:

└───src
    │   index.html
    │   main.ts
    │   styles.css
    │
    └───app
        │   app.config.ts
        │   app.css
        │   app.html
        │   app.routes.ts
        │   app.spec.ts
        │   app.ts
        │
        ├───model
        │       user.model.ts
        │
        ├───services
        │       user.service.ts
        │
        ├───user-create
        │       user-create.css
        │       user-create.html
        │       user-create.spec.ts
        │       user-create.ts
        │
        ├───user-list
        │       user-list.css
        │       user-list.html
        │       user-list.spec.ts
        │       user-list.ts
        │
        └───user-update
                user-update.css
                user-update.html
                user-update.spec.ts
                user-update.ts

user.model.ts

export interface User {
  id?: number;
  first_name: string;
  last_name: string;
  email: string;
  password: string;
  image: File | null;
}

 

user.service.ts

 

import { Injectable } from ‘@angular/core’;
import { HttpClient } from ‘@angular/common/http’;
import { Observable } from ‘rxjs’;
import { User } from ‘../models/user.model’;  // Import the User model
@Injectable({
  providedIn: ‘root’  // Standalone service
})
export class UserService {
   private apiUrl = ‘http://localhost:5000/api/’;  // API URL for backend (Flask or other)
  constructor(private http: HttpClient) { }
 
  // Get all users
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl+‘users’);
  }
  // Create a new user
  createUser(user: FormData): Observable<any> {
    return this.http.post(this.apiUrl+‘users’, user);
  }
  // Get a single user by ID
getUserById(id: number): Observable<User> {
  return this.http.get<User>(`${this.apiUrl}users/${id}`);
}
  // Update an existing user
  updateUser(id: number, user: FormData): Observable<any> {
    return this.http.put(`${this.apiUrl}users/${id}`, user);
  }
  // Delete a user
  deleteUser(id: number): Observable<any> {
    return this.http.delete(`${this.apiUrl}users/${id}`);
  }
}

App.html

<div class=“container-fluid”>
<nav class=“navbar navbar-expand-lg navbar-light bg-light container” >
  <a class=“navbar-brand” href=“#”>Navbar</a>
  <button class=“navbar-toggler” type=“button” data-toggle=“collapse” data-target=“#navbarNav” aria-controls=“navbarNav” aria-expanded=“false” aria-label=“Toggle navigation”>
    <span class=“navbar-toggler-icon”></span>
  </button>
  <div class=“collapse navbar-collapse” id=“navbarNav”>
    <ul class=“navbar-nav”>
      <li class=“nav-item active”>
        <a class=“nav-link” href=“#”>Home <span class=“sr-only”>(current)</span></a>
      </li>
      <li class=“nav-item”>
        <a class=“nav-link” href=“#”>Features</a>
      </li>
      <li class=“nav-item”>
        <a class=“nav-link” href=“#”>Pricing</a>
      </li>
      <li class=“nav-item”>
        <a class=“nav-link disabled” href=“#”>Disabled</a>
      </li>
    </ul>
  </div>
</nav>
</div>
<router-outlet />

 

app.config.ts

 

import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from ‘@angular/core’;
import { provideRouter } from ‘@angular/router’;
import { routes } from ‘./app.routes’;
import { provideHttpClient } from ‘@angular/common/http’;
export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
      provideHttpClient(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes)
  ]
};

 

app.routes.ts

 

import { provideRouter, Routes } from ‘@angular/router’;
import { UserList } from ‘./user-list/user-list’;
import { UserUpdate } from ‘./user-update/user-update’;
import { bootstrapApplication } from ‘@angular/platform-browser’;
import { UserCreate } from ‘./user-create/user-create’;
import { App } from ‘./app’;
export const routes: Routes = [
        { path: , redirectTo: ‘users’, pathMatch: ‘full’ },
        { path: ‘users’, component: UserList },
        { path: ‘users/create’, component: UserCreate },
        { path: ‘users/edit/:id’, component: UserUpdate },
];
export const AppRoutes = provideRouter(routes);
bootstrapApplication(App, {
    providers: [AppRoutes]
});

user-create.ts

import { Component } from ‘@angular/core’;
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from ‘@angular/forms’;
import { UserService } from ‘../services/user.service’;
import { Router, RouterModule } from ‘@angular/router’;
import { CommonModule } from ‘@angular/common’;
@Component({
  selector: ‘app-user-create’,
  imports: [FormsModule, CommonModule,ReactiveFormsModule],
  templateUrl: ‘./user-create.html’,
  styleUrl: ‘./user-create.css’,
  providers:[UserService]
})
export class UserCreate {
    userForm: FormGroup;
  constructor(
    private fb: FormBuilder,
    private userService: UserService,
    private router: Router
  ) {
    this.userForm = this.fb.group({
      first_name: [, Validators.required],
      last_name: [, Validators.required],
      email: [, [Validators.required, Validators.email]],
      password: [, Validators.required],
      image: [null]
    });
  }
   onFileSelected(event: any): void {
    const file = event.target.files[0];
    if (file) {
      this.userForm.patchValue({ image: file });
      this.userForm.get(‘image’)!.updateValueAndValidity();
    }
  }
   createUser(): void {
    if (this.userForm.invalid) return;
    const formData = new FormData();
    formData.append(‘first_name’, this.userForm.get(‘first_name’)!.value);
    formData.append(‘last_name’, this.userForm.get(‘last_name’)!.value);
    formData.append(’email’, this.userForm.get(’email’)!.value);
    formData.append(‘password’, this.userForm.get(‘password’)!.value);
    const imageFile = this.userForm.get(‘image’)!.value;
    if (imageFile) {
      formData.append(‘image’, imageFile, imageFile.name);
    }
    this.userService.createUser(formData).subscribe(() => {
      this.router.navigate([‘/users’]);
    });
  }
}

user-create.html

<div class=“container mt-5”>
  <div class=“card shadow-sm”>
    <div class=“card-body”>
      <h2 class=“card-title mb-4”>Add New User</h2>
      <form [formGroup]=userForm (ngSubmit)=createUser()>
        <div class=“mb-3”>
          <label for=“first_name” class=“form-label”>First Name</label>
          <input type=“text” id=“first_name” class=“form-control” formControlName=“first_name” required>
        </div>
        <div class=“mb-3”>
          <label for=“last_name” class=“form-label”>Last Name</label>
          <input type=“text” id=“last_name” class=“form-control” formControlName=“last_name” required>
        </div>
        <div class=“mb-3”>
          <label for=“email” class=“form-label”>Email</label>
          <input type=“email” id=“email” class=“form-control” formControlName=“email” required>
        </div>
        <div class=“mb-3”>
          <label for=“password” class=“form-label”>Password</label>
          <input type=“password” id=“password” class=“form-control” formControlName=“password” required>
        </div>
        <div class=“mb-3”>
          <label for=“image” class=“form-label”>Profile Image</label>
          <input type=“file” id=“image” class=“form-control” (change)=onFileSelected($event)>
        </div>
        <button type=“submit” class=“btn btn-primary” [disabled]=userForm.invalid>
          Add User
        </button>
      </form>
    </div>
  </div>
</div>

user-list.ts

import { Component, OnInit } from ‘@angular/core’;
import { User } from ‘../models/user.model’;
import { UserService } from ‘../services/user.service’;
import { Router, RouterModule } from ‘@angular/router’;
import { CommonModule } from ‘@angular/common’;
//import { HttpClientModule } from ‘@angular/common/http’;
@Component({
  selector: ‘app-user-list’,
 imports: [CommonModule,RouterModule],
 providers: [UserService] ,
  templateUrl: ‘./user-list.html’,
  styleUrl: ‘./user-list.css’
})
export class UserList implements OnInit {
    users: User[] = [];
    constructor(private userService: UserService, private router: Router) {}
  ngOnInit(): void {
    this.getUsers();
  }
  getUsers() {
    this.userService.getUsers().subscribe((data: User[]) => {
      this.users = data;  // Store the fetched users
     // console.log(this.users)
    });
  }
   deleteUser(id: any) {
    this.userService.deleteUser(id).subscribe(() => {
      this.getUsers();  // Refresh the user list after deletion
    });
  }
  editUser(id: any ) {
    this.router.navigate([`/users/edit/${id}`]);  // Navigate to the update component
  }
}

user-list.html

<div class=“container mt-4”>
  <div class=“d-flex justify-content-between align-items-center mb-3”>
    <h2 class=“mb-0”>User List</h2>
    <a routerLink=“/users/create” class=“btn btn-primary”>Add User</a>
  </div>
  <table class=“table table-bordered table-hover” *ngIf=users.length > 0>
    <thead class=“table-light”>
      <tr>
        <th>ID</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email</th>
        <th>Image</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor=let user of users>
        <td>{{ user.id }}</td>
        <td>{{ user.first_name }}</td>
        <td>{{ user.last_name }}</td>
        <td>{{ user.email }}</td>
        <td>
          <img *ngIf=user.image [src]=‘http://localhost:5000/uploads/photos/’ + user.image alt=“User Image” width=“80” class=“img-thumbnail” />
        </td>
        <td>
          <button (click)=editUser(user.id) class=“btn btn-success btn-sm me-2”>Edit</button>
          <button (click)=deleteUser(user.id) class=“btn btn-danger btn-sm”>Delete</button>
        </td>
      </tr>
    </tbody>
  </table>
  <p *ngIf=users.length === 0 class=“text-muted”>No users found.</p>
</div>

user-update.ts

import { CommonModule } from ‘@angular/common’;
import { Component } from ‘@angular/core’;
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from ‘@angular/forms’;
import { UserService } from ‘../services/user.service’;
import { ActivatedRoute, Router } from ‘@angular/router’;
@Component({
  selector: ‘app-user-update’,
  templateUrl: ‘./user-update.html’,
  styleUrl: ‘./user-update.css’,
  imports: [FormsModule, CommonModule,ReactiveFormsModule],
  providers:[UserService]
})
export class UserUpdate {
 
 userForm: FormGroup;
  userId!: number;
  constructor(
    private fb: FormBuilder,
    private userService: UserService,
    private route: ActivatedRoute,
    private router: Router
  ) {
    this.userForm = this.fb.group({
      first_name: [, Validators.required],
      last_name: [, Validators.required],
      email: [, [Validators.required, Validators.email]],
      password: [, Validators.required],
      image: [null]
    });
  }
    ngOnInit(): void {
    this.userId = Number(this.route.snapshot.paramMap.get(‘id’));
    if (this.userId) {
      this.userService.getUserById(this.userId).subscribe(user => {
        this.userForm.patchValue({
          first_name: user.first_name,
          last_name: user.last_name,
          email: user.email,
          password: user.password
        });
      });
    }
  }
  onFileSelected(event: any): void {
    const file = event.target.files[0];
    if (file) {
      this.userForm.patchValue({ image: file });
      this.userForm.get(‘image’)!.updateValueAndValidity();
    }
  }
  updateUser(): void {
    if (this.userForm.invalid) return;
    const formData = new FormData();
    formData.append(‘first_name’, this.userForm.get(‘first_name’)!.value);
    formData.append(‘last_name’, this.userForm.get(‘last_name’)!.value);
    formData.append(’email’, this.userForm.get(’email’)!.value);
    formData.append(‘password’, this.userForm.get(‘password’)!.value);
    const imageFile = this.userForm.get(‘image’)!.value;
    if (imageFile) {
      formData.append(‘image’, imageFile, imageFile.name);
    }
    this.userService.updateUser(this.userId, formData).subscribe(() => {
      this.router.navigate([‘/users’]);
    });
  }
}

user-update.html

<div class=“container mt-5”>
  <div class=“card shadow-sm”>
    <div class=“card-body”>
      <h2 class=“card-title mb-4”>Add New User</h2>
      <form [formGroup]=userForm (ngSubmit)=updateUser()>
        <div class=“mb-3”>
          <label for=“first_name” class=“form-label”>First Name</label>
          <input type=“text” id=“first_name” class=“form-control” formControlName=“first_name” required>
        </div>
        <div class=“mb-3”>
          <label for=“last_name” class=“form-label”>Last Name</label>
          <input type=“text” id=“last_name” class=“form-control” formControlName=“last_name” required>
        </div>
        <div class=“mb-3”>
          <label for=“email” class=“form-label”>Email</label>
          <input type=“email” id=“email” class=“form-control” formControlName=“email” required>
        </div>
        <div class=“mb-3”>
          <label for=“password” class=“form-label”>Password</label>
          <input type=“password” id=“password” class=“form-control” formControlName=“password” required>
        </div>
        <div class=“mb-3”>
          <label for=“image” class=“form-label”>Profile Image</label>
          <input type=“file” id=“image” class=“form-control” (change)=onFileSelected($event)>
        </div>
        <button type=“submit” class=“btn btn-primary” [disabled]=userForm.invalid>
          Add User
        </button>
      </form>
    </div>
  </div>
</div>

Leave a Reply

Your email address will not be published. Required fields are marked *