W Angular 2 aplikacja zbudowana jest z drzewa komponentów. Każdy komponent może mieć zestaw komponentów „dzieci” oraz rodzica. Naszym głównym komponentem jest jego korzeń, tzw. root component
. Wpis rozpoczyna się od tego miejsca, choć wiedza dotycząca szablonów jest tutaj bardzo istotna.
Pierwszy komponent
Na początku nasza aplikacja złożona jest z jednego komponentu, będącego rodzicem całej struktury drzewiastej. Komponenty reprezentowane są przez klasy, a jej składowe publiczne (atrybuty, metody) dostępne są z poziomu szablonu HTML komponentu. Dla jasności, poniżej zamieszczam kod, który omawiamy:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor() {}
}
Czym jest @Component?
Dekorator @Component
wzbogaca naszą klasę o odpowiednie metadane, które pozwalają identyfikować nasze klasy jako komponenty. Celem metadanych (na powyższym przykładzie) jest poinformowanie jaki selektor reprezentuje nasz komponent (w tym wypadku <app-root>
) oraz wskazanie plików zawierających szablon i style.
Metadane
Oczywiście, to nie wszystkie konfiguracyjne metadane, które możemy zdefiniować z poziomu dekoratora @Component
. Poniżej tabela zawierająca pełny spis dostępnych ustawień:
selector | Określa selektor, który reprezentuje nasz komponent. Wspierane są podstawowe selektory CSS, takie jak element , .class , [atrybut] oraz :not() . |
templateUrl | Ścieżka do pliku zawierającego szablon komponentu. ./app.component.html' |
template | Szablon komponentu w postaci ciągu znaków (string). '<div><p>Hello world!</p></div>' |
styleUrls | Lista ścieżek do plików zawierających style. ['./app.component.css'] |
styleUrls | Lista stringów zawierających style. ['a {color: black}'] |
providers | Lista wstrzykiwanych zależności (ten parametr omówiony zostanie w artykule dot. services ). |
viewProviders | Lista wstrzykiwanych zależności z uwzględnieniem ng-content . |
moduleId | Ustawienie parametru na wartość module.id pozwala na korzystanie z relatywnych ścieżek do plików. W naszym przypadku – bez znaczenia (korzystamy z Webpacka). |
changeDetection | Parametr determinujący zachowanie naszego ChangeDetector . Wrócimy do tego parametru w artykule dot. optymalizacji aplikacji. |
encapsulation | Parametr decydujący o hermetyzacji stylów CSS komponentu. Wrócimy do tego parametru w artykule dot. enkapsulacji CSS. |
Uwaga! Poza wymienionymi ustawieniami, dekorator
@Component()
może przyjmować dodatkowo takie atrybuty jakinputs
czyhost
. Jako, że korzystamy z TypeScripta, nam te parametry nie są do niczego potrzebne.
Cykl życia
Każdy komponent posiada swój cykl życia. Oznacza to, że może zostać m.in zainicjalizowany, wyrenderowany czy zniszczony, a my możemy na te zdarzenia zareagować (np. zwolnić zasoby).
Reagować możemy poprzez implementację odpowiedniego interfejsu, co w konsekwencji oznacza utworzenie metod o konkretnej sygnaturze. Nazewnictwo jest proste do zapamiętania, przykładowo inicjalizacja obsługiwana jest przez metodę ngOnInit
, a interfejs, który to wymusza nosi nazwę OnInit
(bez przedrostka ng
). Ta konwencja dotyczy wszystkich zdarzeń. Poniżej pełny spis:
constructor() |
Konstruktor wykorzystujemy niemalże jedynie do wstrzykiwania zależności. Wrócimy do tego przy okazji omawiania serwisów. |
ngOnChanges(change) |
Zdarzenie wywoływane przy każdej zmianie składowych @Input() (wrócimy do tego w artykule dot. komunikacji pomiędzy komponentami). |
ngOnInit() |
Zdarzenie wywoływane po inicjalizacji składowych @Input() , pierwszym zdarzeniu ngOnChanges() . |
ngDoCheck() |
Zdarzenie wywoływane przy każdorazowej detekcji zmian składowych komponentu. |
ngAfterViewInit() |
Zdarzenie wywoływane po inicjalizacji szablonu komponentu (przed ngAfterContentInit() ). |
ngAfterViewChecked() |
Zdarzenie wywoływane po każdorazowej detekcji zmian składowych komponentu. |
ngAfterContentInit() |
Zdarzenie wywoływane po inicjalizacji ng-content . |
ngAfterContentChecked() |
Zdarzenie wywoływane po każdorazowej detekcji zmian składowych z ng-content . |
ngOnDestroy() |
Zdarzenie wywoływane przed zniszczeniem instancji komponentu. |
Interakcja z widokiem
Tak jak wspomniałem na samym początku, klasa komponentu definiuje składowe – atrybuty i metody dostępne z poziomu szablonu. Wzbogaćmy nasz główny komponent o zestaw właściwości oraz udostępnijmy widokowi metodę – click()
, która m.in. będzie zliczała ilość kliknięć. Dorzucimy też obsługę zdarzenia ngDoCheck()
.
@Component({..})
export class AppComponent implements DoCheck {
heading = 'Before click';
list: string[] = [];
doCheckFires = 0;
counter = 0;
ngDoCheck() {
++this.doCheckFires;
}
click(): void {
this.counter++;
this.heading = 'After click';
this.list = [ 'Label 1', 'Label 2', 'Label 3' ];
}
}
Przejdźmy teraz do szablonu naszego komponentu. Jeżeli coś będzie dla Ciebie niezrozumiałe, zerknij tutaj.
<div class="container">
<h5>{{ heading }}</h5>
<p [style.font-weight]="counter > 0 ? '700' : '400'">Click counter: {{ counter }}</p>
<p>DoCheck fires: {{ doCheckFires }} times</p>
<ul class="collection" *ngIf="list.length > 0">
<li class="collection-item" *ngFor="let item of list">
<p>{{ item }}</p>
</li>
</ul>
<button type="button" class="btn red accent-2" (click)="click()">Click</button>
</div>
Po skompilowaniu, tak powinien prezentować się rezultat naszej pracy:
Podsumowanie
Celem tego artykułu było zapoznanie się z podstawową wiedzą dotyczącą budowania komponentów w Angular 2. Na ten moment pominąłem sporo zagadnień – m.in. temat kompozycji komponentów czy komunikacji pomiędzy nimi. Te kwestię poruszę w następnych wpisach, tak, aby wszystko miało ręce i nogi.
Pełny kod źródłowy dostępny na GitHub.