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 jakinputsczyhost. 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.