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