diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7aabb85 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing Guidelines + +Thanks for contributing to this Angular training repository. + +## Workflow + +1. Fork the repository and create a feature branch. +2. Keep changes focused and small. +3. Run build checks locally before submitting. +4. Open a pull request with a clear description. + +## Coding Standards + +- Follow Angular style conventions. +- Prefer strict typing over `any`. +- Keep feature code modular. + +## Pull Request Checklist + +- [ ] Code compiles successfully +- [ ] Changes are scoped to the requested feature/fix +- [ ] Documentation is updated when needed diff --git a/README.md b/README.md index d08b1a8..09a7890 100644 --- a/README.md +++ b/README.md @@ -1,264 +1,97 @@ -## Angular Training +# Formation Angular -NVM Install For Windows -https://github.com/coreybutler/nvm-windows/releases/download/1.2.2/nvm-setup.zip -``` -Installation Steps : - 1-Download and run the nvm-setup.exe installer - 2-Select the NVM installation path (e.g., C:\nvm) - 3-Select the Node.js installation path (e.g., C:\nodejs) - 4-Confirm the installation -``` -NVM Install For Linux/MacOS -Using the Installation Script +![Angular](https://img.shields.io/badge/Angular-21-dd0031?logo=angular&logoColor=white) +![Node.js](https://img.shields.io/badge/Node.js-20%2B-339933?logo=node.js&logoColor=white) +![Status](https://img.shields.io/badge/status-ready_for_training-success) -Open a terminal and execute one of the following commands: -``` -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash -``` -Or: -``` -wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash -``` -Version compatibility : https://angular.dev/reference/versions -``` -1 - working with angular 13/14 : - nvm use 14.15 - npm i @angular/cli@13 -2 - working with angular 21 - nouveatés depuis la version 15 - 21 - * Les Standalone Components - * Les Signals - * Les Signal Forms (expérimental) - * Nouvelle syntaxe de contrôle de flux (@if, @for, @switch - nvm use 22 ou 24 - npm i @angular/cli@21 -``` -1️⃣ Créer une application Angular -``` -ng new formation-angular -cd formation-angular -ng serve -``` -use backend api : -``` -https://github.com/Invivoo/spring-crud -``` -test : -```sh - http://localhost:4200 -``` -Project structure version 13/14 -``` - . - ├── nodes_modules - └── src - ├── app - │   ├── app.component.css - │   ├── app.component.html - │   ├── app.component.spec.ts - │   ├── app.component.ts - │   ├── app.module.ts - │   ├── app.routing.module.ts - | - └── index.html - └── main.ts - └── styles.css - └── test.ts - ├── angular.json - ├── package.json - ├── package-lock.json - ├── README.md - ├── tsconfig.app.json - ├── tsconfig.app - ├── tsconfig.spec.json - -``` +A practical Angular training starter focused on user CRUD concepts and modular project organization. -Project structure version 21 -``` - . - ├── nodes_modules - └── src - ├── app - │   ├── app.config.ts - │   ├── app.css - │   ├── app.html - │   ├── app.routes.ts - │   ├── app.spec.ts - │   └── app.ts - └── index.html - └── main.ts - └── styles.css - ├── angular.json - ├── package.json - ├── package-lock.json - ├── README.md - ├── tsconfig.app.json - ├── tsconfig.app - ├── tsconfig.spec.json - -``` +## Quick Start -2️⃣ Services api and crud components - -1 add module users on app/ directory -``` -ng g module users --routing +```bash +npm install +npm start ``` -2 add model folder : -``` -add user.model.ts -``` -3 add a new component : -``` -ng g component crud -``` -4 add services folder : -``` -ng g service users -``` -``` -└── src - ├── app - │   ├── users - │   ├── model - │   ├── user.model.ts - │   ├── services - │   ├── users.service.ts - │   ├── crud - │   ├── crud.component.css - │   ├── crud.component.html - │   ├── crud.component.ts - │   ├── users.module.ts - │   ├── users-routing.module.ts - │   ├── app.component.html - │   ├── app.component.spec.ts - │   ├── app.component.ts - │   ├── app.module.ts - │   ├── app.routing.module.ts -``` -- install bootstrap - ``` - npm install --save bootstrap - ``` - - update styles.css - ``` add - @import "bootstrap/dist/css/bootstrap.min.css"; - ``` -3️⃣ users.serice.ts -```ts -import { Injectable } from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {Observable} from 'rxjs'; -import {UserModel} from '../model/user.model'; -const baseUrl = 'http://localhost:8081/api/v1/employees'; +Then open: [http://localhost:4200](http://localhost:4200) -@Injectable({ - providedIn: 'root' -}) -export class UsersService { +## Table of Contents - constructor(private http: HttpClient) {} +- [Overview](#overview) +- [Setup](#setup) +- [Project Structure](#project-structure) +- [Development Steps](#development-steps) +- [Related Resources](#related-resources) +- [Contributing](#contributing) - getUsers(): Observable { - return this.http.get(baseUrl); - } +## Overview - create(data: any): Observable { - return this.http.post(baseUrl, data); - } +This repository provides a clean Angular foundation with: - update(id: any, data: any): Observable { - return this.http.put(`${baseUrl}/${id}`, data); - } +- App bootstrap and root shell +- `users` feature module with routing +- User model and API service +- CRUD list + reusable user card component +- Bootstrap global styling - delete(id: any): Observable { - return this.http.delete(`${baseUrl}/${id}`); - } +## Setup - findByEmail(email: any): Observable { - return this.http.get(`${baseUrl}?email=${email}`); - } -} +Detailed instructions: [SETUP.md](./SETUP.md) -``` -- crud.component.ts -```ts - export class CrudComponent implements OnInit { +Quick compatibility notes: - userList: UserModel[] = []; +- Angular: 21+ +- Node.js: 20+ +- npm: 10+ - constructor(private usersService: UsersService) {} +## Project Structure - ngOnInit(): void { - this.usersService.getUsers().subscribe(rep => { - this.userList = rep - }); - } -. -. +```text . -``` -- add a user-card component - add : ```@Input() user: UserModel;``` - -- user-card component html : -```html --
-
USER
-
-
{{ user.firstName }} {{ user.lastName }}
-

- {{ user.email }} -

-
-
-``` -- crud.component.html -```html -
- -
-``` -- app.compoent.html - add : - ```html - - -``` - -- routing app.routing.module.ts -```ts -const routes: Routes = [ - {path: '', component: AppComponent}, - {path: 'users', loadChildren: () => import('../users/users.module').then(m => m.UsersModule)} - ]; -``` -- routing users.routing.module.ts -```ts -const routes: Routes = [ - {path: 'all', component: CrudComponent}, - {path: 'create', component: AddUserComponent}, -]; -``` - - +├── angular.json +├── package.json +├── tsconfig.json +├── src +│ ├── index.html +│ ├── main.ts +│ ├── styles.css +│ └── app +│ ├── app.component.ts +│ ├── app.component.html +│ ├── app.component.css +│ └── users +│ ├── users.module.ts +│ ├── users-routing.module.ts +│ ├── model +│ │ └── user.model.ts +│ ├── services +│ │ └── users.service.ts +│ ├── crud +│ │ ├── crud.component.ts +│ │ ├── crud.component.html +│ │ └── crud.component.css +│ └── user-card +│ ├── user-card.component.ts +│ ├── user-card.component.html +│ └── user-card.component.css +├── CONTRIBUTING.md +└── SETUP.md +``` + +## Development Steps + +1. Install dependencies. +2. Start the Angular dev server. +3. Ensure backend API is running: [Invivoo/spring-crud](https://github.com/Invivoo/spring-crud) +4. Open `http://localhost:4200` and navigate to **Users List**. + +## Related Resources + +- [Angular Documentation](https://angular.dev) +- [Angular Version Compatibility](https://angular.dev/reference/versions) +- [Bootstrap Documentation](https://getbootstrap.com/docs) +- [RxJS Documentation](https://rxjs.dev) + +## Contributing + +Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening issues or pull requests. diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..0fda0e0 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,36 @@ +# Setup Guide + +## Prerequisites + +- Node.js 20+ +- npm 10+ + +## Installation + +```bash +npm install +``` + +## Start Development Server + +```bash +npm start +``` + +Application URL: [http://localhost:4200](http://localhost:4200) + +## Build + +```bash +npm run build +``` + +## Backend API + +This frontend expects a backend API, for example: + +- https://github.com/Invivoo/spring-crud + +Expected users endpoint used by the app: + +- `http://localhost:8081/api/v1/employees` diff --git a/angular.json b/angular.json new file mode 100644 index 0000000..f3b1bce --- /dev/null +++ b/angular.json @@ -0,0 +1,70 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "formation-angular": { + "projectType": "application", + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/formation-angular", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.json", + "assets": [], + "styles": [ + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "600kb", + "maximumError": "1.2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "formation-angular:build:production" + }, + "development": { + "buildTarget": "formation-angular:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + }, + "cli": { + "analytics": false + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9337265 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "formation-angular", + "version": "1.0.0", + "private": true, + "description": "Angular training starter project", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development" + }, + "dependencies": { + "@angular/common": "^21.2.4", + "@angular/compiler": "^21.2.4", + "@angular/core": "^21.2.4", + "@angular/forms": "^21.2.4", + "@angular/platform-browser": "^21.2.4", + "@angular/platform-browser-dynamic": "^21.2.4", + "@angular/router": "^21.2.4", + "bootstrap": "^5.3.3", + "rxjs": "~7.8.0", + "zone.js": "~0.15.1" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^21.2.4", + "@angular/cli": "^21.2.4", + "@angular/compiler-cli": "^21.2.4", + "typescript": "~5.9.3" + } +} diff --git a/src/app/app.component.css b/src/app/app.component.css new file mode 100644 index 0000000..3d856ad --- /dev/null +++ b/src/app/app.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + min-height: 100vh; +} diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100644 index 0000000..03584b5 --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1,17 @@ + + +
+ +
diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..f8e2455 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { RouterLink, RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [RouterLink, RouterOutlet], + templateUrl: './app.component.html', + styleUrl: './app.component.css', +}) +export class AppComponent {} diff --git a/src/app/users/crud/crud.component.css b/src/app/users/crud/crud.component.css new file mode 100644 index 0000000..5d4e87f --- /dev/null +++ b/src/app/users/crud/crud.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/app/users/crud/crud.component.html b/src/app/users/crud/crud.component.html new file mode 100644 index 0000000..35ff42d --- /dev/null +++ b/src/app/users/crud/crud.component.html @@ -0,0 +1,14 @@ +
+

Users

+ +

Loading users...

+

{{ error }}

+ +
+
+ +
+ +

No users found.

+
+
diff --git a/src/app/users/crud/crud.component.ts b/src/app/users/crud/crud.component.ts new file mode 100644 index 0000000..c0edabc --- /dev/null +++ b/src/app/users/crud/crud.component.ts @@ -0,0 +1,36 @@ +import { Component, DestroyRef, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +import { UserModel } from '../model/user.model'; +import { UsersService } from '../services/users.service'; + +@Component({ + selector: 'app-crud', + standalone: false, + templateUrl: './crud.component.html', + styleUrl: './crud.component.css', +}) +export class CrudComponent implements OnInit { + private readonly usersService = inject(UsersService); + private readonly destroyRef = inject(DestroyRef); + + users: UserModel[] = []; + loading = true; + error = ''; + + ngOnInit(): void { + this.usersService + .getUsers() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: (users) => { + this.users = users; + this.loading = false; + }, + error: () => { + this.error = 'Unable to load users at this time.'; + this.loading = false; + }, + }); + } +} diff --git a/src/app/users/model/user.model.ts b/src/app/users/model/user.model.ts new file mode 100644 index 0000000..79e3323 --- /dev/null +++ b/src/app/users/model/user.model.ts @@ -0,0 +1,6 @@ +export interface UserModel { + id?: number; + firstName: string; + lastName: string; + email: string; +} diff --git a/src/app/users/services/users.service.ts b/src/app/users/services/users.service.ts new file mode 100644 index 0000000..30ad446 --- /dev/null +++ b/src/app/users/services/users.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { UserModel } from '../model/user.model'; + +const USERS_API_URL = 'http://localhost:8081/api/v1/employees'; + +@Injectable({ + providedIn: 'root', +}) +export class UsersService { + constructor(private readonly http: HttpClient) {} + + getUsers(): Observable { + return this.http.get(USERS_API_URL); + } + + create(data: UserModel): Observable { + return this.http.post(USERS_API_URL, data); + } + + update(id: number, data: Partial): Observable { + return this.http.put(`${USERS_API_URL}/${id}`, data); + } + + delete(id: number): Observable { + return this.http.delete(`${USERS_API_URL}/${id}`); + } + + findByEmail(email: string): Observable { + return this.http.get(`${USERS_API_URL}?email=${encodeURIComponent(email)}`); + } +} diff --git a/src/app/users/user-card/user-card.component.css b/src/app/users/user-card/user-card.component.css new file mode 100644 index 0000000..fe475b9 --- /dev/null +++ b/src/app/users/user-card/user-card.component.css @@ -0,0 +1,3 @@ +.card-header { + font-weight: 600; +} diff --git a/src/app/users/user-card/user-card.component.html b/src/app/users/user-card/user-card.component.html new file mode 100644 index 0000000..858c3f5 --- /dev/null +++ b/src/app/users/user-card/user-card.component.html @@ -0,0 +1,7 @@ +
+
USER
+
+
{{ user.firstName }} {{ user.lastName }}
+

{{ user.email }}

+
+
diff --git a/src/app/users/user-card/user-card.component.ts b/src/app/users/user-card/user-card.component.ts new file mode 100644 index 0000000..04019a1 --- /dev/null +++ b/src/app/users/user-card/user-card.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; + +import { UserModel } from '../model/user.model'; + +@Component({ + selector: 'app-user-card', + standalone: false, + templateUrl: './user-card.component.html', + styleUrl: './user-card.component.css', +}) +export class UserCardComponent { + @Input({ required: true }) user!: UserModel; +} diff --git a/src/app/users/users-routing.module.ts b/src/app/users/users-routing.module.ts new file mode 100644 index 0000000..5e95596 --- /dev/null +++ b/src/app/users/users-routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrudComponent } from './crud/crud.component'; + +const routes: Routes = [ + { path: '', pathMatch: 'full', redirectTo: 'all' }, + { path: 'all', component: CrudComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class UsersRoutingModule {} diff --git a/src/app/users/users.module.ts b/src/app/users/users.module.ts new file mode 100644 index 0000000..457ace1 --- /dev/null +++ b/src/app/users/users.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { CrudComponent } from './crud/crud.component'; +import { UserCardComponent } from './user-card/user-card.component'; +import { UsersRoutingModule } from './users-routing.module'; + +@NgModule({ + declarations: [CrudComponent, UserCardComponent], + imports: [CommonModule, UsersRoutingModule], +}) +export class UsersModule {} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..150ee1e --- /dev/null +++ b/src/index.html @@ -0,0 +1,12 @@ + + + + + FormationAngular + + + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..466f5da --- /dev/null +++ b/src/main.ts @@ -0,0 +1,18 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter, Routes } from '@angular/router'; + +import { AppComponent } from './app/app.component'; + +const routes: Routes = [ + { path: '', pathMatch: 'full', redirectTo: 'users/all' }, + { + path: 'users', + loadChildren: () => import('./app/users/users.module').then((m) => m.UsersModule), + }, + { path: '**', redirectTo: 'users/all' }, +]; + +bootstrapApplication(AppComponent, { + providers: [provideHttpClient(), provideRouter(routes)], +}).catch((error: unknown) => console.error(error)); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..e9ab6d5 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,7 @@ +@import "bootstrap/dist/css/bootstrap.min.css"; + +body { + margin: 0; + font-family: Arial, Helvetica, sans-serif; + background-color: #f8f9fa; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2365867 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": [ + "ES2022", + "dom" + ], + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +}