Tribu Angular
Oxyl - 2023
NGULAR
v15.x.x
npm install @angular/cli
Initial Request
Form Post
Client
Server
Page reload !
Initial Request
XHR
{ ... } JSON
Client
Server
Avantages de Typescript
Qu'est-ce que Typescript ?
export class MonComponent extends MaClassParente implements MonInterface {
private maVariable: number | boolean | string = 'abc';
protected array: any[];
public abc: null | undefined;
static readonly CONST = "CONSTANTE";
constructor(private readonly service: Service) {}
maFonction(attr: number): void {
const constante = 123;
let variable = "abc";
let monArrowFunction = (a, b) => a+b;
}
}
Template
< >
Component
{ }
Injector
Service
{ }
LoginModule
{}
{{ person.name }}
(click)="handle()"
Metadata
Directive
{ }
Metadata
UserModule
{}
CardModule
{}
ListModule
{}
@NgModule({
declarations: any[],
imports: any[],
exports: any[],
providers: any[],
...
})
export MonMondule {}
Life Cycle Hooks
@Component({
selector: 'parent-component',
template: '<child-component></child-component>',
styleUrls: string[],
...
})
class ParentComponent {}
@Component({
selector: 'child-component',
template: 'Salut les grands malades !'
})
class ChildComponent {}
@Injectable({
providedIn: 'root'
})
class MonService {}
class MonComponent {
constructor(private readonly service: Service) {}
}
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
<p appHighlight>Je suis surligné</p>
<p>Pas moi</p>
<div *ngIf="expressionBooleenne">
Hello
</div>
<ul>
<li *ngFor="let item of itemList">
{{ item }}
</li>
</ul>
{{ value }}
[property]="value"
(event)="handlerFunction($event)"
[(ngModel)]="property"
DOM
Component
<div>
Hello {{ name }}
</div>
<input [src]="imageUrl" type="image">
@Component(...)
class ImageComponent {
imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/c/cf/Angular_full_color_logo.svg';
}
<button (click)="doSomething()">
Click me !
</button>
Liste des HTML DOM Events sans le "on" :
click, dblclick, mousedown, keypress, change, resize, blur, focus, ...
Root Component
Root Template
< >
Root Class
{ }
Child A Component
Child A Template
< >
Child A Class
{ }
Child B Component
Child B Template
< >
Child B Class
{ }
Grandchild Component
Grandchild Template
< >
Grandchild Class
{ }
ng new oxyl-cocktail
> CSS ?
> SCSS
> Routing ?
> Yes
ng serve
Ou
npm start
Pour nous aider côté design
Installation :
ng add @angular/material
> Theme ? Deep Purple/Amber
> Set up global typography ? Yes
> Animations ? Yes
Dans le répertoire src/app :
ng generate module custom-material
Dans src/app, créer le composant Header :
ng generate component header
Design du composant à l'aide de :
On va voir si vous suivez...
Créez un composant nommé RecipeCard
Générez les 3 fichiers ci-dessous dans le répertoire src/app/models :
export interface Ingredient {
name: string;
id?: number;
}
import { Ingredient } from './ingredient.model';
export interface RecipeIngredient {
id?: number;
ingredient: Ingredient;
quantity: number;
unit: string;
}
import { RecipeIngredient } from './recipe-ingredient.model';
export interface Recipe {
id?: number;
name: string;
picture: string;
description: string;
ingredients: RecipeIngredient[];
instructions: string[];
}
export const MOCK_RECIPES: Recipe[] = [
{
id: 0,
name: 'Daiquiri',
picture:
'https://www.liquor.com/thmb/AN9OCZOXjnCO8_YpiYWNTLZDGjY=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/mango-brava-daiquiri-720x720-primary-4a3e81859e1e497bbd528f71ef09494a.jpg',
description:
`The classic Daiquiri is a super simple rum cocktail that’s well-balanced and refreshing.
The combination of sweet, sour and spirit is refreshingly tangy and perfect for any occasion.`,
ingredients: [
{
ingredient: {
name: 'Dark rum (Appleton Estate Reserve)',
},
quantity: 2,
unit: 'oz'
},
{
ingredient: {
name: 'Fresh lime juice',
},
quantity: 1,
unit: 'oz'
},
{
ingredient: {
name: 'Simple sirup',
},
quantity: 1,
unit: 'oz'
}
],
instructions: [
'Add all the ingredients to a shaker and fill with ice.',
'Shake, and strain into a chilled Martini glass.',
'Garnish with a lime wheel.'
]
},
{
id: 1,
name: 'Piña Colada',
picture:
'https://www.liquor.com/thmb/hmc01qQqlwI0H1od1Qw0me4LEjI=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/__opt__aboutcom__coeus__resources__content_migration__liquor__2019__02__13090826__pina-colada-720x720-recipe-253f1752769447f6998afd2b9469c24e.jpg',
description:
`The Piña Colada is a classic tropical cocktail with rum, pineapple and coconut milk.
This classic recipe will transport you to paradise. Getting caught in the rain is not required.`,
ingredients: [
{
ingredient: {
name: 'Light or gold rum',
},
quantity: 1.5,
unit: 'oz'
},
{
ingredient: {
name: 'Coconut milk',
},
quantity: 2,
unit: 'oz'
},
{
ingredient: {
name: 'Fresh pineapple juice',
},
quantity: 2,
unit: 'oz'
}
],
instructions: [
'Add all the ingredients to a shaker and fill with ice.',
'Shake, and strain into a Hurricane glass filled with fresh ice.',
'Garnish with a cherry and a pineapple wedge.'
]
},
{
id: 2,
name: 'Mojito',
picture:
'https://www.liquor.com/thmb/G6gVUxrTRCesHawcaUYl9ITSNmA=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/mojito-720x720-primary-6a57f80e200c412e9a77a1687f312ff7.jpg',
description:
`To many people, the Mojito represents the perfect rum cocktail. The origins of the drink can be traced back to
Cuba and the 16th-century Cuban cocktail El Draque, named for Sir Francis Drake. `,
ingredients: [
{
ingredient: {
name: 'Mint leaves',
},
quantity: 6,
unit: ''
},
{
ingredient: {
name: 'Simple syrup',
},
quantity: 0.75,
unit: 'oz'
},
{
ingredient: {
name: 'Fresh lime juice',
},
quantity: 0.75,
unit: 'oz'
},
{
ingredient: {
name: 'White rum',
},
quantity: 1.5,
unit: 'oz'
},
{
ingredient: {
name: 'Club soda',
},
quantity: 1.5,
unit: 'oz'
}
],
instructions: [
'In a shaker, lightly muddle the mint.',
'Add the simple syrup, lime juice and rum, and fill with ice.',
'Shake well and pour (unstrained) into a highball glass.',
'Top with the club soda and garnish with a mint sprig.'
]
},
{
id: 3,
name: 'Dirty Martini',
picture:
'https://www.liquor.com/thmb/rSFRMIErR5V0GN1eQMobHHwX498=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/dirty-martini-1500x1500-hero-6cbd60561031409ea1dbf1657d05cb2d.jpg',
description:
'A dash of olive brine brings a salty, savory note to the all-time classic.',
ingredients: [
{
ingredient: {
name: 'Gin or vodka',
},
quantity: 2.5,
unit: 'oz'
},
{
ingredient: {
name: 'Dry vermouth',
},
quantity: 0.5,
unit: 'oz'
},
{
ingredient: {
name: 'Olive brine',
},
quantity: 0.5,
unit: 'oz'
}
],
instructions: [
'Add all the ingredients to a mixing glass filled with ice.',
'Stir, and strain into a chilled cocktail glass.',
'Garnish with 2 olives.'
]
}
];>// TODO : add mock recipes here
Paramètre d'entrée du composant :
@Input()
recipe: Recipe;
Design du composant à l'aide de :
Affichage d'une liste de recettes
Composant Recipe List :
ng generate component recipe-list
Afficher la liste :
<app-recipe-card *ngFor="let recipe of recipes"
[recipe]="recipe">
</app-recipe-card>
Afficher / masquer les détails :
<button (click)="toggleIngredients()">
See more
</button>
Si vous êtes en avance :
On se synchronise (😎) pour la suite des explications sur l'asynchrone
Process
A
Process
B
Process
A
Process
B
Process A en attente tant que B n'est pas terminé
Process A se poursuit en attendant que B se termine
Processus Synchrone
Processus Synchrone
Récupération des données de l'Observable
Dans un composant appelant le getRecipes() du service.
this.recipeService.getRecipes().subscribe(
(result: Recipe[]) => {
// Traiter le résultat
},
(error) => {
// Traiter l'erreur
}
);
Générer le service RecipeService
ng generate service recipe
Service RecipeService
Refacto permettant de gérer l'asynchrone :
getRecipes(): Observable<Recipe[]> {
return of(RECIPES_MOCK);
}
Utilisation du HttpClient :
imports: [
...,
HttpClientModule,
...
]
AppModule :
Créer un fichier : app/environments/dev.ts
export const API_URL = 'http://52.47.189.244:8080/api/v1';
dev.ts :
Utilisation du HttpClient :
private readonly recipeUrl = `${API_URL}/recipes`
constructor(private http: HttpClient) { }
getRecipes(): Observable<Recipe[]> {
return this.http.get<Recipe[]>(`${this.recipeUrl}`);
}
RecipeService :
Passage au HttpClient :
getRecipe(id: number): Observable<Recipe> {
return this.http.get<Recipe>(`${ this.recipeUrl }/${ id }`);
}
RecipeService, ajout d'une requête "by ID" :
const routes: Routes = [
{
path: 'recipes',
component: RecipeListComponent,
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'recipes',
pathMatch: 'full'
}
];
AppRoutingModule
@NgModule({
exports: [
RouterModule
],
imports: [
RouterModule.forRoot(routes)
]
})
<router-outlet></router-outlet>
Point d'entrée des routes
AppModule
SharedModule
CoreModule
RecipeModule
...Module
const routes: Routes = [
{
path: '',
component: RecipeListComponent
},
];
const routes: Routes = [
{
path: '',
redirectTo: '/recipes',
pathMatch: 'full'
},
{
path: 'recipes',
loadChildren: () => import('./recipe/recipe.module')
.then(m => m.RecipeModule)
}
];
app-routing.module.ts
recipe-routing.module.ts
TIPS :
TIPS :
Dans la page de détail :
Pipes : Transformer les données
@Pipe({
name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {
transform(param : InputType): OutputType {
doSomething();
return newValue;
}
}
Tests unitaires !
Karma + Jasmine
Animations : Animer les composants
https://angular.io/guide/animations#setup