Angular
Mise en place d'un projet Typescript/Angular
Sommaire - 1ère partie
1. Présentation
2. angular-cli
3. Typescript
4. Templates & Components
5. Services & DI
Présentation
. Ceci n'est pas AngularJS!
. Version 4.4.5
. Angular 5 le 23 Octobre

Angular-CLI
> npm install -g @angular/cli
> ng new adoptabeer --style=scss > cd adoptabeer > ng serve
> ng serve --env=mock --app=whishy
> ng build
Angular-CLI
> ng g component my-new-component > ng g directive my-new-directive > ng g pipe my-new-pipe > ng g service my-new-service > ng g module my-new-module > ng g guard my-new-guard > ng g class my-new-class > ng g interface my-new-interface > ng g enum my-new-enum
Typescript
. Made by Miscrosoft.
. Compilation en JS.
. Totale compatibilité avec le code JS existant.
. Langage normalisé, implémente des fonctionnalités du futur ECMAScript 6.

Typescript
. Typage
. Classes, interfaces & héritage
. Génériques
. Décorateurs
. Fichiers de définition
. Mixins
. Développement modulaire
Typescript
export class Beer {
id: number;
name: string;
type: BeerType;
ratings: number[];
averageRating: number;
constructor(pId: number) { this.id = pId; }
public addRating(rating: number) {
this.ratings.push(rating);
}
}
Typescript
export class Beer {
private _id: number;
public get id(): number {
return this._id;
}
constructor(pId: number) { this.id = pId; }
}
Typescript
export class BelgianBeer extends Beer {
constructor(pId: number) {
super(pId);
this.averageRating = 10;
}
}
Typescript
export abstract class Beer {
abstract make(): void;
}
export class Kwak extends Beer {
public make(): void {
// Implementation
}
}
Typescript
export interface Beer {
id: number;
name: string;
type: BeerType;
ratings?: number[];
}
let beer: Beer = {
id: 1,
name: 'Kwak'
}; // WRONG
Typescript
export interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 5, y: 10 };
point.x = 10; // NOPE!
Typescript
export interface OnInit {
ngOnInit(): void;
}
export class HomeComponent implements OnInit { ngOnInit(): void { console.log('bla bla'); } }
Typescript
export enum BeerType {
BLOND = 0,
RED = 1,
AMBER = 2
}
export type BeerType = 'blond' | 'red';
Typescript
var awfulBeerName: any = '1664'; // NOPE!!!
let beerRating: number = 6; // let: can be reassigned
const maxBeerRating: number = 10; // const: can not be reassigned
const availableBeers:Beer[] = [ leffe, kwak ];
Typescript
// And many more features... // Union types
let a: ClassA | ClassB | null;
let b: Array<ClassA>;
let c: number | string;
c = [10, "Hey, I'm a tuple!"];
console.log(`${c[0]} ${c[1]}`);
// This is a tuple!
...
AdoptABeer Use Case
. Une bière a un id numérique, un nom, un type (blonde, rouge, blanche, ambrée), une description facultative, un pourcentage d'alcool, et une note moyenne. Une bière est considérée "pour enfant" quand son degré d'alcool est inférieur ou égal à 6°.
. Un utilisateur a un mail/password, un nom, prénom et nom complet (nom & prénom). Il peut donner une note, et une seule, à une bière.
@Component
. Représente le template et la logique pour un bloc métier.
. Template & logique associée.
. A éviter:
1. Manipulation DOM
2. Mise à jour du modèle de données
3. Appels "externes" (HTTP, etc.)
4. Interactions avec des composants externes
Component
@Component({
selector: 'app-zip-code',
templateUrl: './zip-code.component.html',
styleUrls: ['./zip-code.component.scss']
})
export class ZipCodeComponent implements OnInit {
...
Component
<div> Please enter your zip code: <br> <app-zip-code></app-zip-code> </div>
View component
@Component({
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
View component
http://localhost:4200/home
const routes: Routes = [
{path: 'home', component: HomeComponent},
...
Interpolation
. home.component.html:
<div>{{ beerName }}</div>
. home.component.ts:
@Component({
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class AppComponent {
beerName = 'Kwak';
}
Property binding
. home.component.html:
<span [innerHTML]="beerName"></span> <div [class.bubble_arrow]="showArrow">...</div>
. home.component.ts:
@Component({
templateUrl: './home.component.html'
})
export class AppComponent {
showArrow: boolean = true;
beerName: string = '<b>Cuvée des trolls</b>';
Event binding
. home.component.html:
<button (click)="onCancel()">Annuler</button>
. home.component.ts:
@Component({
templateUrl: './home.component.html'
})
export class AppComponent {
onCancel(): void {
console.log('Cancel button cliked');
}
Two way data binding
. home.component.html:
<span>Saisir le nom de la bière:</span> <input [(ngModel)]="beerName"> <button (click)="onReset()">Reset</button>
. home.component.ts:
export class AppComponent {
beerName: string;
onReset() :void { this.beerName = null; }
Angular data binding

Directives
<div *ngIf="beer !== null" >{{beer.name}}</div>
<div *ngIf="favoriteBeer; else noBeerBlock"> You love {{favoriteBeer.name}}. </div> <ng-template #noBeerBlock> <a href="#" (click)="setFavoriteBeer()"> Set your favorite beer. </li> </ng-template>
Directives
<ul> <li *ngFor="let beer of beers"> {{beer.name}} </li> </ul>
Directives
<div [ngSwitch]="beer?.type"> <div *ngSwitchCase="'blond'">...</div>
<div *ngSwitchCase="'red'">...</div> </div>
AdoptABeer Use Case
> git clone https://github.com/LWroblewski/adoptabeer.git
. Dans le header: gérer le cas de l'utilisateur connecté/non-connecté.
. Dans la vue home: lister dynamiquement les bières.
. Pour les plus rapides, appliquer des filtres (par type, etc.).
Services
@Injectable()
export class BeerService {
findBeers(): Beer[] { ... }
}
export class Home Component {
constructor(private beerService: BeerService){
}
public findBeers(): Beer[] {
return this.beerService.findBeers();
}
AdoptABeer Use Case
. Factoriser la logique métier bières/utilisateurs dans des services associés.
. Factoriser l'IHM de visu des informations d'une bière dans un composant.
@Input
input.component.ts:
export class InputComponent {
@Input()
showPlaceHolder: boolean;
home.component.html:
<input-component [showPlaceHolder]="false"></input-component>
<input-component [showPlaceHolder]="show"></input-component>
@Input
header.component.ts:
export class HeaderComponent {
@Input()
title: string;
app.component.html:
<app-header [title]="'AdoptBeer'"></app-header>
<app-header title="AdoptBeer"></input-component>
@Input
header.component.ts:
export class HeaderComponent {
@Input('headerTitle')
title: string;
app.component.html:
<app-header [headerTitle]="'AdoptBeer'"></app-header>
<app-header headerTitle="AdoptBeer"></input-component>
@Output
header.component.ts:
export class HeaderComponent {
@Output()
logged: EventEmitter<User> = new EventEmitter();
public login(): void {
...
...
this.logged.emit(newUser);
}
@Output
app.component.html:
<app-header (logged)="onUserLogged($event)"></app-header>
app.component.ts:
export class HeaderComponent {
public onUserLogged(newUser: User) {
...
Container & Presentation
. Containers (Smart) components
VS
. Presentation (Dumb) components
Container & Presentation
. Séparation des responsabilités:
. Code de l'application (recherche des données, etc)
. Code purement lié à l'UI
. Meilleure réutilisabilité des components
Container & Presentation
export class DumbBeerComponent {
@Input beer: Beer;
}
export class SmartBeerComponent {
beer: Beer;
ngOnInit() {
this.beer = this.beerService.getBeer(idBeer);
this.beer.status = ...;
...
....
}
AdoptABeer Use Case
. Création d'un BeerComponent générique, avec des description d'une bière passée en paramètre, et avec des méthodes génériques like, et click.
httpClient
@Injectable()
export class BeerService {
constructor(private http: HttpClient) { }
public getBeers(): Observable<Beer[]> {
return this.http.get<Beer[]>('data/beers.json');
}
}
Implémentation...
export class HomeComponent implements OnInit {
beers: Beer[];
constructor(public beerService: BeerService){ }
ngOnInit() {
this.beerService.getBeers()
.subscribe(function (beers: Beer[]) {
this.beers = beers;
});
}

Implémentation...
<p> <span *ngFor="let beer of beers"> {{ beer.name }} </span> </p>

Implémentation...
export class HomeComponent implements OnInit {
beers: Beer[];
constructor(public beerService: BeerService){ }
ngOnInit() {
this.beerService.getBeers()
.subscribe(function (beers: Beer[]) {
this.beers = beers;
}.bind(this));
}

Implémentation...
<p *ngIf="beers"> <span *ngFor="let beer of beers"> {{ beer.name }} </span> </p>

Implémentation...
export class HomeComponent implements OnInit {
beers: Beer[];
constructor(public beerService: BeerService){ }
ngOnInit() {
this.beerService.getBeers()
.map(res => res.json())
.subscribe(beers => this.beers = beers);
}
Implémentation...
export class HomeComponent implements OnInit {
beers$: Observable<Beer[]>;
constructor(public beerService: BeerService){ }
ngOnInit() {
this.beers$ = this.beerService.getBeers();
}
Implémentation...
<p *ngIf="beers$ | async as beers"> <span *ngFor="let beer of beers"> {{ beer.name }} </span> </p>
Power of the observables
this.suggestions$ = this.control.valueChanges
.startWith(initialCityValue)
.filter(value => value !== null)
.distinctUntilChanged()
.switchMap(zipCode => this.zipCodeSuggest.suggest(zipCode));
Petit use case...
. Sur une vue detail_beer, on souhaite afficher l'ensemble des infos d'une bière donnée, à partir de son id.
. Problème: les informations sont dispersées dans diverses APIs.
Petit use case...
this.beerDetail$: Observable<BeerDetail> = forkJoin( this.beerService.getBeer(idBeer), this.beerService.getBeerPrices(idBeer), this.beerService.getBeerStocks(idBeer), this.beerService.getBeerSuggestions(idBeer) ).map(([beer, prices, stoks, suggestions]) => { ...beer, ...prices, ...stocks, ...suggestions });
Angular - formation
By Laurent WROBLEWSKI
Angular - formation
- 489