Compare commits

..

4 Commits

Author SHA1 Message Date
Florian THIERRY
a497d2cd25 test 2023-06-09 10:25:25 +02:00
Florian THIERRY
4ca604503c Renommage de code 2023-02-22 10:14:31 +01:00
Florian THIERRY
0fffd47436 Petites améliorations UI/UX. 2023-02-21 17:05:21 +01:00
Florian THIERRY
cfd4838d17 Ajout d'un exemple pour les appels http. 2023-02-21 15:25:03 +01:00
24 changed files with 379 additions and 92 deletions

22
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"@angular/platform-browser": "^15.1.0",
"@angular/platform-browser-dynamic": "^15.1.0",
"@angular/router": "^15.1.0",
"angular-in-memory-web-api": "^0.15.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
@@ -4382,6 +4383,19 @@
"ajv": "^8.8.2"
}
},
"node_modules/angular-in-memory-web-api": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.15.0.tgz",
"integrity": "sha512-T07vQTeBEGWfwZRM+jIooYgK4c8LR25I27otIthUkFINAREoPiBNogWDxlnROIeu1k2RrY3QJjMOYq7f62UcgA==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "^15.0.0",
"@angular/core": "^15.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -15346,6 +15360,14 @@
"fast-deep-equal": "^3.1.3"
}
},
"angular-in-memory-web-api": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.15.0.tgz",
"integrity": "sha512-T07vQTeBEGWfwZRM+jIooYgK4c8LR25I27otIthUkFINAREoPiBNogWDxlnROIeu1k2RrY3QJjMOYq7f62UcgA==",
"requires": {
"tslib": "^2.3.0"
}
},
"ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",

View File

@@ -20,6 +20,7 @@
"@angular/platform-browser": "^15.1.0",
"@angular/platform-browser-dynamic": "^15.1.0",
"@angular/router": "^15.1.0",
"angular-in-memory-web-api": "^0.15.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"

View File

@@ -4,12 +4,17 @@ import {PromisesExampleComponent} from "./promises-example/promises-example.comp
import {HomeComponent} from "./home/home.component";
import {ObservablesExampleComponent} from "./observables-example/observables-example.component";
import {StateManagementExampleComponent} from "./state-management-example/state-management-example.component";
import {NetworkCallComponent} from "./network-call/network-call.component";
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'network-call',
component: NetworkCallComponent
},
{
path: 'promises',
component: PromisesExampleComponent

View File

@@ -3,3 +3,6 @@
<app-back-to-home></app-back-to-home>
<router-outlet></router-outlet>
</div>
<div>
test
</div>

View File

@@ -12,9 +12,11 @@ import {BackToHomeComponent} from './back-to-home/back-to-home.component';
import {MatIconModule} from "@angular/material/icon";
import {StateManagementExampleComponent} from './state-management-example/state-management-example.component';
import {RestServicesModule} from "./core/rest-services/rest-services.module";
import {CarComponent} from './core/components/car/car.component';
import {ObservablesExampleModule} from "./observables-example/observables-example.module";
import {CoreModule} from "./core/core.module";
import {NetworkCallModule} from "./network-call/network-call.module";
import {HttpClientInMemoryWebApiModule} from "angular-in-memory-web-api";
import {RestApiMockService} from "./core/rest-api-mock/rest-api-mock.service";
@NgModule({
declarations: [
@@ -33,7 +35,14 @@ import {CoreModule} from "./core/core.module";
MatButtonModule,
MatIconModule,
RestServicesModule,
ObservablesExampleModule
ObservablesExampleModule,
NetworkCallModule,
HttpClientInMemoryWebApiModule.forRoot(
RestApiMockService,
{
dataEncapsulation: false
}
)
],
providers: [],
bootstrap: [AppComponent]

View File

@@ -3,7 +3,8 @@ import {CommonModule} from '@angular/common';
import {CarComponent} from "./components/car/car.component";
import {RestServicesModule} from "./rest-services/rest-services.module";
import {MaterialModule} from "./material.module";
import {HttpClientInMemoryWebApiModule} from "angular-in-memory-web-api";
import {RestApiMockService} from "./rest-api-mock/rest-api-mock.service";
@NgModule({
declarations: [
@@ -12,13 +13,13 @@ import {MaterialModule} from "./material.module";
imports: [
CommonModule,
RestServicesModule,
MaterialModule
MaterialModule,
],
exports: [
CarComponent,
RestServicesModule,
MaterialModule
]
],
})
export class CoreModule {
}

View File

@@ -2,11 +2,15 @@ import {NgModule} from "@angular/core";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTooltipModule} from "@angular/material/tooltip";
import {MatSnackBar, MatSnackBarModule} from "@angular/material/snack-bar";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
const IMPORTED_MATERIAL_MODULES = [
MatIconModule,
MatButtonModule,
MatTooltipModule
MatIconModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatTooltipModule,
]
@NgModule({

View File

@@ -1,5 +1,5 @@
export interface Car {
id: string;
id: number;
brand: string;
model: string;
power: number;

View File

@@ -0,0 +1,37 @@
import {Injectable} from '@angular/core';
import {InMemoryDbService} from "angular-in-memory-web-api";
import {Car} from "../model/car";
@Injectable({
providedIn: 'root'
})
export class RestApiMockService implements InMemoryDbService {
createDb() {
const cars: Car[] = [
{
id: 1,
brand: 'Toyota',
model: 'Yaris',
power: 12,
numberOfSeats: 5
},
{
id: 2,
brand: 'Citroën',
model: 'DS',
power: 14,
numberOfSeats: 5
},
{
id: 3,
brand: 'Renault',
model: 'Twingo',
power: 8,
numberOfSeats: 2
}
];
return {
cars
};
}
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Car} from "../model/car";
import {toPromise} from "../utils/promises.utils";
import {Observable} from "rxjs";
@Injectable({
providedIn: 'root'
@@ -12,7 +13,11 @@ export class CarRestService {
private http: HttpClient
) { }
findById(carId: string): Promise<Car> {
return toPromise(this.http.get<Car>(`/cars/${carId}`));
findById(carId: number): Promise<Car> {
return toPromise(this.findById$(carId));
}
findById$(carId: number): Observable<Car> {
return this.http.get<Car>(`/api/cars/${carId}`);
}
}

View File

@@ -8,6 +8,10 @@ export interface Link {
}
const LINKS: Link[] = [{
label: 'Les appels réseaux - Promesses ou Observables ?',
icon: '🌍',
href: '/network-call'
}, {
label: 'Les promesses',
icon: '🙏',
href: '/promises'

View File

@@ -0,0 +1,26 @@
<div class="component">
<h1>Les appels réseaux - Promesses ou Observables ?</h1>
<div class="actions">
<button mat-raised-button
(click)="loadCarViaAPromise()"
[disabled]="isLoading$ | async">
🙏 Load the car via a Promise
</button>
<button mat-raised-button
(click)="loadCarViaAnObservable()"
[disabled]="isLoading$ | async">
👁️ Load the car via an Observable
</button>
</div>
<div class="loading" *ngIf="isLoading$ | async">
<h2>Loading...</h2>
<mat-spinner></mat-spinner>
</div>
<div class="section" *ngIf="isLoaded">
<h2>Car data</h2>
<div class="centered">
<app-car [value]="car"></app-car>
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
.component {
.actions {
display: flex;
gap: 1rem;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
}
}

View File

@@ -0,0 +1,79 @@
import {Component} from '@angular/core';
import {BehaviorSubject, catchError, EMPTY, finalize, Observable, tap} from "rxjs";
import {Car} from "../core/model/car";
import {CarRestService} from "../core/rest-services/car.rest-service";
import {MatSnackBar} from "@angular/material/snack-bar";
const CAR_ID = 1;
@Component({
selector: 'app-network-call',
templateUrl: './network-call.component.html',
styleUrls: ['./network-call.component.scss']
})
export class NetworkCallComponent {
private isLoadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
isLoaded: boolean = false;
car?: Car;
constructor(
private carRestService: CarRestService,
private snackBar: MatSnackBar
) {
}
get isLoading$(): Observable<boolean> {
return this.isLoadingSubject.asObservable();
}
loadCarViaAPromise(): void {
this.loadThroughAPromise();
}
loadCarViaAnObservable(): void {
this.loadThroughAnObservable();
}
private loadThroughAPromise(): void {
this.isLoadingSubject.next(true);
this.isLoaded = false;
this.carRestService.findById(CAR_ID)
.then(car => {
this.car = car;
this.isLoaded = true;
})
.catch(error => {
console.log('An error occured while loading the car.', error);
this.snackBar.open(
'An error occured while loading the car.',
'Close',
{duration: 2000}
);
})
.finally(() => this.isLoadingSubject.next(false));
}
private loadThroughAnObservable(): void {
this.isLoadingSubject.next(true);
this.isLoaded = false;
this.carRestService.findById$(CAR_ID)
.pipe(
tap(car => {
this.car = car;
this.isLoaded = true;
}),
catchError(error => {
console.log('An error occured while loading the car.', error);
this.snackBar.open(
'An error occured while loading the car.',
'Close',
{duration: 2000}
);
return EMPTY;
}),
finalize(() => this.isLoadingSubject.next(false))
).subscribe();
}
}

View File

@@ -0,0 +1,21 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {NetworkCallComponent} from "./network-call.component";
import {CoreModule} from "../core/core.module";
import {HttpClientModule} from "@angular/common/http";
@NgModule({
declarations: [
NetworkCallComponent
],
imports: [
CommonModule,
CoreModule,
HttpClientModule
],
exports: [
NetworkCallComponent
]
})
export class NetworkCallModule {
}

View File

@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ObservablesExampleComponent } from './observables-example.component';
describe('ObservablesExampleComponent', () => {
let component: ObservablesExampleComponent;
let fixture: ComponentFixture<ObservablesExampleComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ObservablesExampleComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ObservablesExampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -6,5 +6,10 @@ import { Component } from '@angular/core';
styleUrls: ['./observables-example.component.scss']
})
export class ObservablesExampleComponent {
/*
Formulaires :
-> valueChanges / debounceTime etc...
Appel réseau -> pour voir la diff avec les promesses
*/
}

View File

@@ -17,7 +17,7 @@ export class SimpleObservableExempleComponent {
addNewCar(): void {
const newCar: Car = {
id: `${Math.random() * 100}`,
id: Math.random() * 100,
brand: 'Toyota',
model: 'Yaris',
power: 12,

View File

@@ -1,4 +1,27 @@
<div class="component">
<h1>Les promesses</h1>
<div class="actions">
<button mat-raised-button (click)="showCarViaAPromise()" [disabled]="isLoading">
🙏 Show car via a Promise
</button>
<button mat-raised-button (click)="showCarViaAsyncAwaitProcess()" [disabled]="isLoading">
⏱️ Show car via an Async Await process
</button>
</div>
<div class="section">
Number of loadings: {{numberOfLoadings}}
</div>
<div class="loading" *ngIf="isLoading">
<h2>Loading...</h2>
<mat-spinner></mat-spinner>
</div>
<div class="section" *ngIf="isLoaded">
<h2>Car data</h2>
<div class="centered">
<app-car [value]="car"></app-car>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
.component {
.actions {
display: flex;
gap: 1rem;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
}
}

View File

@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PromisesExampleComponent } from './promises-example.component';
describe('PromisesExampleComponent', () => {
let component: PromisesExampleComponent;
let fixture: ComponentFixture<PromisesExampleComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PromisesExampleComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(PromisesExampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,5 +1,14 @@
import {Component} from '@angular/core';
import {Car} from "../core/model/car";
import {MatSnackBar} from "@angular/material/snack-bar";
const CAR: Car = {
id: 1,
brand: 'Toyota',
model: 'Yaris',
power: 12,
numberOfSeats: 5
};
@Component({
selector: 'app-promises-example',
@@ -7,11 +16,80 @@ import {Car} from "../core/model/car";
styleUrls: ['./promises-example.component.scss']
})
export class PromisesExampleComponent {
car?: Car = {
id: '1234567890',
brand: 'Toyota',
model: 'Yaris',
power: 12,
numberOfSeats: 5
};
car?: Car;
isLoading: boolean = false;
isLoaded: boolean = false;
numberOfLoadings: number = 0;
constructor(
private snackBar: MatSnackBar
) {
}
// ******************************************
// Mode Promise avec les then/catch/finally
// ******************************************
showCarViaAPromise(): void {
this.loadCarViaAPromise();
}
private loadCarViaAPromise(): void {
this.isLoading = true;
this.isLoaded = false;
this.findViaPromise()
.then(car => {
this.car = car;
this.isLoaded = true;
})
.catch(error => {
this.snackBar.open(`Error while loading car. Cause : ${error}`, 'Close', { duration: 5000 });
})
.finally(() => {
this.isLoading = false;
this.numberOfLoadings++;
});
}
private findViaPromise(): Promise<Car> {
return new Promise<Car>((resolve, reject) => {
if (this.numberOfLoadings % 2 === 0) {
setTimeout(() => {
resolve(CAR);
}, 2000);
} else {
reject('It\'s an odd call number.');
}
});
}
// ******************************************
// Mode async/await
// ******************************************
showCarViaAsyncAwaitProcess(): void {
this.loadCarViaAsyncAwaitProcess();
}
private async loadCarViaAsyncAwaitProcess(): Promise<void> {
this.isLoading = true;
this.isLoaded = false;
try {
this.car = await this.findViaAsyncAwaitProcess();
this.isLoaded = true;
} catch (error) {
this.snackBar.open(`Error while loading car. Cause : ${error}`, 'Close', { duration: 5000 });
}
this.numberOfLoadings++;
this.isLoading = false;
}
private async findViaAsyncAwaitProcess(): Promise<Car> {
if (this.numberOfLoadings % 2 !== 0) {
throw 'It\'s an odd call number.';
}
await new Promise(resolve => setTimeout(resolve, 2000));
return CAR;
}
}

View File

@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StateManagementExampleComponent } from './state-management-example.component';
describe('StateManagementExampleComponent', () => {
let component: StateManagementExampleComponent;
let fixture: ComponentFixture<StateManagementExampleComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ StateManagementExampleComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(StateManagementExampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -2,3 +2,13 @@
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.centered {
display: flex;
flex-direction: row;
justify-content: center;
}
.section {
margin: 1rem 0;
}