Javascript

Kurs Angular 2 – Komponenty

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 jak inputs czy host. 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:

ng

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.