komunikacja @OUTPUT

omówię:

– jak przesłać dane z komponetu dziecka do komponentu rodzica
- użycie adnotacji @Output oraz EventEmittera
- kastowanie przy użyciu `!!`

W tej lekcji chciałbym wprowadzić możliwość wyboru jednego z samochodów oraz możliwość powrócenia do ekranu wyboru.

Przeniesiemy teraz informacje o dostępnych samochodach z szablonu HTML do komponentu. 

Niezbędnym krokiem będzie utworzenie reprezentacji samochodu w kodzie. W chwili obecnej jest ona zaszyta w HTML’u i nie jest to rozwiązanie docelowe. 

Stwórz folder car w katalogu app. Następnie utwórz plik car.interface.ts  i dodaj w nim poniższy kod.

export interface Car {
brand: string;
model: string;
vin: string;
engineType: string;
type: string;
price: number;
photoSource: string;
}

Stworzyłeś właśnie reprezentację samochodu. Pewnie zdziwi Cię fakt, że w Angularze reprezentacja jest tworzona przy pomocy interfejsu a nie klasy. Wynika to z faktu, że podczas pobierania danych z backendu domyślnym sposobem mapowania danych jest przetworzenie ich właśnie na interfejs. Pokażę Ci to w dalszej części. Zaprezentuję Ci także przypadki w których stworzymy reprezentację samochodu jako klasę czyli takie DTO z Javy. Na chwilę obecną samochód będzie reprezentowany przy pomocy interfejsu.

Przejdź teraz do klasy app.component.ts i utwórz tymczasową listę która przechowuje wszystkie dostępne samochody do wyboru. (W kolejnej lekcji będziemy pobierać dane z serwisu oraz backendu.)

import { Component } from '@angular/core';
import { Car } from './car/car.interface';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
cars: Car[] = [
{
brand: 'AUDI',
model: 'RS7',
vin: '1HGEJ814XVL168593',
engineType: 'petrol',
type: 'sedan',
price: 250450,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/3-small.jpeg'
},
{
brand: 'Mercedes',
model: 'GT',
vin: '1C3BC1FB1BN610126',
engineType: 'petrol',
type: 'coupe',
price: 598900,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/4-small.jpeg'
},
{
brand: 'AUDI',
model: 'A3',
vin: '3C7WDTCL6CG300411',
engineType: 'petrol',
type: 'sedan',
price: 170000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/5-small.jpeg'
},
{
brand: 'Mercedes',
model: 'KLASA G',
vin: '1G1YY32G425115593',
engineType: 'petrol',
type: 'suv',
price: 654000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/6-small.jpeg'
},
{
brand: 'BMW',
model: 'SERIA 3',
vin: '2HNYD18281H533966',
engineType: 'petrol',
type: 'coupe',
price: 73400,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/7-small.jpeg'
},
{
brand: 'Nissan',
model: '370z',
vin: '4M2DU86W22ZJ33397',
engineType: 'petrol',
type: 'coupe',
price: 125000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/8-small.jpeg'
}
];
}

Posiadasz już listę dostępnych samochodów, wyświetlmy ją teraz za pomocą wbudowanej dyrektywy *ngFor. Jest to nic innego jak zwykła pętla for tylko przeznaczona dla szablonu HTML. Przejdź do pliku app.component.html.

<div class="main-container">
<app-card *ngFor="let car of cars"
[title]="car.brand"
[subtitle]="car.model"
[photoSource]="car.photoSource"
[description]="'CENA: ' + car.price + ' ZŁ. VIN: ' + car.vin">
</app-card>
</div>

Gdy przejdziesz do aplikacji zauważysz, że wygląda za wiele się nie zmienił. Przeniesliśmy natomiast dane o samochodach do komponetu oraz uprościliśmy kod szablonie HTML.

Popracujmy teraz nad możliwością wyboru samochodu. Niezbędne będzie wykorzystanie adnotacji @Output. Adnotacja ta pozwala na komunikację od dziecka do rodzica. (Odwrotnie niż @Input). Z adnotacją @Output silnie powiązany jest EventEmitter, służy on do reagowania na zdarzenie np. kliknięcie w przycisku w danym komponencie. Po złapaniu zdarzenia mamy możliwość emitowania wartości na zewnątrz komponentu w górę w stronę rodzica. 

Przejdź do klasy card.component.ts. Chcemy wykorzystać ten komponent w dwóch kontekstach przed wybraniem samochodu i po jego wybraniu. Spójrz na poniższy kod

import {Component, EventEmitter, Input, Output} from '@angular/core';

@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent {
@Input()
public photoSource: string;
@Input()
public title: string;
@Input()
public subtitle: string;
@Input()
public description: string;
@Input()
public identifier: string;
@Input()
public isCarSelected: boolean; // 4
@Output()
public carSelected = new EventEmitter<string>(); // 1
@Output()
public returnToMainWindow = new EventEmitter(); // 1

public selectedCar(): void { // 2
this.carSelected.emit(this.identifier);
}

public backToMainWindow(): void { // 2
this.returnToMainWindow.emit();
}
}

Przejdz do pliku card.component.html

<mat-card class="example-card">
<mat-card-header>
<mat-card-title>{{title}}</mat-card-title>
<mat-card-subtitle>{{subtitle}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{photoSource}}">
<mat-card-content>
<p>{{description}}</p>
</mat-card-content>
<mat-card-actions>
<button *ngIf="!isCarSelected" mat-raised-button color="primary"
(click)="selectedCar()">WYBIERZ</button> <!--3-->
<button *ngIf="isCarSelected" mat-raised-button color="primary"
(click)="backToMainWindow()">WRÓĆ</button> <!--3-->
</mat-card-actions>
</mat-card>

Zmiany jakie tutaj zaszły to :

1) dodanie zmiennej oznaczonej adnotacją @Output, która jest typy EventEmitter

2) dodanie metod, które są odpowiedzialne za reagowanie na kliknięcie przycisku z punktu 3 i emitują wartość do komponentu wyżej. 

3) użycie metod w szablonie HTML. Gdy wybierzesz samochód zostanie odpalona metoda selectedCar, która emituje do komponetu app.component.ts wartość vin po której możemy wyszukać dany samochód i ustawić go jako wybrany.

4) dodanie flagi, która steruje wyświetleniem odpowiedniego przycisku w punkcie 3

Przejdź do pliku app.component.ts. Musimy jeszcze połączyć zmienną @Output z komponentu card.componet.ts z szablonem app.component.html i odpowiednią metodą z komponetu app.component.ts

Spójrz na poniższy kod

<div class="container">
<div class="card-container" *ngIf="!selectedCar; else carInfo"> <!--5-->
<app-card *ngFor="let car of cars" class="card"
[identifier]="car.vin"
[title]="car.brand"
[subtitle]="car.model"
[photoSource]="car.photoSource"
[description]="'CENA: ' + car.price + ' ZŁ. VIN: ' + car.vin"
(carSelected)="carSelected($event)"> <!--6-->
</app-card>
</div>
<ng-template #carInfo>
<app-card class="info-card"
[identifier]="selectedCar.vin"
[title]="selectedCar.brand"
[subtitle]="selectedCar.model"
[photoSource]="selectedCar.photoSource"
[description]="'CENA: ' + selectedCar.price + ' ZŁ. VIN: ' + selectedCar.vin"
[isCarSelected]="!!selectedCar"
(returnToMainWindow)="resetCarSection()"> <!--6-->
</app-card>
</ng-template>
</div>
import { Component } from '@angular/core';
import { Car } from './car/car.interface';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
selectedCar: Car; // 8
cars: Car[] = [
{
brand: 'AUDI',
model: 'RS7',
vin: '1HGEJ814XVL168593',
engineType: 'petrol',
type: 'sedan',
price: 250450,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/3-small.jpeg'
},
{
brand: 'Mercedes',
model: 'GT',
vin: '1C3BC1FB1BN610126',
engineType: 'petrol',
type: 'coupe',
price: 598900,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/4-small.jpeg'
},
{
brand: 'AUDI',
model: 'A3',
vin: '3C7WDTCL6CG300411',
engineType: 'petrol',
type: 'sedan',
price: 170000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/5-small.jpeg'
},
{
brand: 'Mercedes',
model: 'KLASA G',
vin: '1G1YY32G425115593',
engineType: 'petrol',
type: 'suv',
price: 654000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/6-small.jpeg'
},
{
brand: 'BMW',
model: 'SERIA 3',
vin: '2HNYD18281H533966',
engineType: 'petrol',
type: 'coupe',
price: 73400,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/7-small.jpeg'
},
{
brand: 'Nissan',
model: '370z',
vin: '4M2DU86W22ZJ33397',
engineType: 'petrol',
type: 'coupe',
price: 125000,
photoSource: 'https://www.szkolaangulara.pl/wp-content/uploads/2020/05/8-small.jpeg'
}
];

public carSelected(vin: string): void { // 7
this.selectedCar = this.cars
.find(car => car.vin === vin);
}

public resetCarSection() { // 7
this.selectedCar = null;
}
}

5) dodaliśmy tutaj instrukcję warunkową odwołującą się do zmiennej selectedCar. Jeżeli nie ma wybranego samochodu pokazujemy widok wyboru, natomiast jeżeli wybraliśmy samochód pokazujemy widok z wybranym samochodem.

Jeżeli klikniesz przycisk WRÓĆ zmienna selectedCar zostanie ustawiona na null co oznacza, że nie ma wybranego samochodu i wrócisz do widoku głównego.

Połączenie zmiennej @Output z komponentu card.componet.ts z szablonem app.component.html zachodzi w punkcie 6 i 7.

6) odwołanie do zmiennej z komponentu dziecka oznaczonej adnotacją @Output odbywa się za pośrednictwem nawiasów okrągłych oraz podaniu nazwy zmiennej (carSelected). Nastepnym krokiem jest przypisanie metody z klasy komponentu (carSelected)=”carSelected($event)”

Możesz zastanawiać się czemu w [isCarSelected]=”!!selectedCar” występuje podwójny wykrzyknik. Jest to kastowanie obiektu na zmienną typu boolean. Jeżeli zmienna selectedCar będzie równa null to po kastowaniu zostanie ustawiona wartość false jeżeli samochód będzie wybrany to kastowanie zwróci wartość true.

7) popatrz na metodę carSelected. Wywoła się ona w momencie gdy komponent card.component.ts wyemituje wartość poprzez wywołanie this.carSelected.emit(this.identifier). Metoda carSelected w komponencie app.component.ts przechwyci wartość, wysłaną przy pomocy emittera, która jest vinem samochodu. To co dzieje się w ciele metody carSelected z punktu 7 to nic innego jak wyszukanie samochodu po vin, ze wszystkich dostępnych samochodów i przypisanie go do zmiennej this.selectedCar.

Dodaj jeszcze zmienione cssy do pliku app.componet.css

.example-card {
max-width: 30%;
margin: 5px;
}

.example-header-image {
background-size: cover;
}


.main-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}

Przejdź do aplikacji localhost:4200

Po wybraniu samochodu powinieneś zobaczyć tylko jego kartę. Po wybraniu samochodu, po prawej stronie pojawiło się miejsce aby utworzyć formularz na podanie danych klienta. Tym będziemy zajmować się w kolejnych lekcjach.