ANGULAR 2 DYNAMIC COMPONENT – tworzymy dynamiczne komponenty

Angular 2 umożliwia nam dynamiczne tworzenie komponentów. Jest to bardzo przydatna funkcjonalność, np. w przypadku tworzenia modali lub w każdym przypadku, kiedy chcemy stworzyć komponent w locie. W tym artykule pokażę jak tworzyć dynamicznie komponenty za pomocą serwisu ComponentFactoryResolver oraz jak się z nimi komunikować z poziomu kontrolera rodzica.

Zagadnienia, które poruszę:

  • dynamiczne tworzenie komponentu za pomocą serwisu ComponentFactoryResolver
  • umiejscowienie dynamicznego komponentu w dowolnym miejscu templatki rodzica
  • przekazanie wartości do dynamicznego komponentu oraz wywoływanie jego metod
  • dodanie dynamicznego komponentu do entryComponents

Każda część artykułu jest zakończona przykładem na Plunkerze. Do ostylowania przykładów posłużyłem się bootstrap 4.

TWORZYMY WZÓR DYNAMICZNEGO KOMPONENTU

Zaczniemy od napisania klasy, która posłuży nam za wzór naszego dynamicznego komponentu.

COMPONENT FACTORY RESOLVER

W celu stworzenia dynamicznego komponentu w komponencie rodzica, zaczniemy od wstrzyknięcia serwisów ComponentFactoryResolver oraz ViewContainerRef do konstruktora rodzica (parent.component.ts):

Oraz stworzymy metodę, która będzie przywołana na kliknięcie buttona, a efektem kliknięcia, będzie dynamicznie stworzenie komponentu.

Przenalizujmy, co się wydarzyło w naszej metodzie createDynamicComponent()

const factory:

  •  do consta przypisaliśmy wartość, którą zwraca nam metoda resolveComponentFactory, którą udostępnia nam serwis componentFactoryResolver
  • Metoda resolveComponentFactory przyjmuje wyłącznie jeden parametr, komponent który ma stworzyć. Zatem jako parametr przekazaliśmy nazwę klasy naszego dynamicznego komponentu. Zwrócona wartość jest typu ComponentFactory.

const componentRef:

  • wołamy metodę createComponent serwisu ViewContainerRef, który reprezentuje kontener, do którego możemy załączyć jeden lub więcej widoków.
  • Kontener może zawierać dwa typy widoków (host views oraz embedded views). W naszym przypadku otrzymamy Host View, który powstaje podczas tworzenia instancji komponentu poprzez metodę createComponent.
  • Nasz kontener widoków domyślnie wskazuje na nasz parent component, więc nasz stworzony dynamicznie komponent, doda się na koniec drzewa DOM widoku parenta.
  • Metoda createComponent może przyjmować 4 parametry, w tym jeden konieczny, czyli fabrykę komponentu o typie ComponentFactory, która szczęśliwie jest trzymana w naszym const factory. Drugi parametr do index, czyli pozycja stworzonego komponentu w kontenerze. W przypadku braku podania indeksu, nowy widok zajmie ostatnią pozycję w kontenerze.
  • Metoda createComponent zwraca typ ComponentRef. ComponentRef reprezentuje instancję komponentu stworzonego poprzez ComponentFactory. ComponentRef umożliwi nam dostęp do pól i metod publicznych naszego dynamicznego komponentu, co przyda nam się później przy przekazywaniu wartości.

componentRef.changeDetectorRef.detectChanges():

  •  jeśli zajrzymy do dokumentacji ComponentRef, widzimy, że implementuje changeDetectorRef, z kolei ten, udostępnia nam metodę detectChanges(). Ta metoda sprawdza changeDetectora i jego dzieci, ale to już temat na odrębny artykuł. W skrócie, wołamy tą metodę aby angular uruchomił lifecycle hooks dynamicznego komponentu oraz mechanizmy detekcji.

Sprawdźmy, czy wszystko działa, live example poniżej:

LIVE EXAMPLE

Wszystko cacy! Z tym, że generowanie komponentu, do którego nie przekazaliśmy żadnych danych i dodatkowo nie określiliśmy gdzie ma się pojawić w drzewie DOM, nie zawiele nam daje.

OKREŚLAMY MIEJSCE W DRZEWIE DOM DYNAMICZNEGO KOMPONENTU

Aby określić miejsce w drzewie DOM, gdzie pojawi się nasz dynamiczny komponent, musimy zmienić kontekst wywołania metody createComponent. Użyjemy do tego dekoratora viewChild(), który umożliwi nam dostęp do local variable w templatce parent component. Użycie local variable da nam referencje do komponentu lub po prostu selektora, np. diva.

Modyfikujemy nasz plik parent.component.html:

Czemu angularowy element template zamiast po prostu DIV? w przypadku przypisania local variable do diva, nasz dynamiczny komponent i tak opuści tego diva podczas renderowania i doda się zaraz za nim. W przypadku wątpliwości, proszę sprawdzić.

Łapiemy refrencję do #dynamicComponentContainer poprzez ViewChild() w parent.component.ts:

Zakładam, że dekorator @ViewChild jest Ci znany, ale metadata property „read”, być może nie.

Poprzez {read: someType}, określasz, jaki typ powinien zwracać local variable określony poprzez #localVariableName.

Jeśli nie określisz property read, to @ViewChild zwraca:

  • ElementRef jeśli #localVariableName nie został zastosowany na komponencie
  • instancję komponentu jeśli #localVariableName został zastosowany na komponencie
  • jeśli chcesz inny typ, np ViewContainerRef, musisz o tym powiedzieć używać metadata property „read”.

Podsumowując, dzięki „read” możemy zmienić typ, który przetrzymuje @ViewChild, na np. ViewContainerRef, którego posiada potrzebną nam metodę createComponent().

Zmieniamy kontekst wywołania createComponent() z viewContainerRef na nasz dynamicComponentContainer.

Zatem wiemy już, jak tworzyć dynamiczny komponent w porządanym przez nas miejscu. Poniżej link do plunkera po dodaniu #dynamicComponentContainer.

LIVE EXAMPLE

PRZEKAZUJEMY WARTOŚCI DO DYNAMICZNEGO KOMPONENTU

Do naszej templatki w dynamic.component.ts dodamy dwa publiczne pola, name oraz index (index będzie określał index w kontenerze – viewContainerRef).

Będziemy przekazywać wartości od rodzica, a nie ma @Input() ? Zgadza się, nie ma. Dynamiczne komponenty nie mają supportu inputa i outputa. Wróćmy do metody createDynamicComponent w parent.component.ts. Dopiszemy parę linijek.

Skupmy się na:

Jak wcześniej pisałem, componentRef trzyma wartość typu ComponentRef,  która przetrzymuje instancję dynamicznego komponentu. Jeśli zajrzymy do dokumentacji klasy ComponentRef, widzimy property Instance. Ta właściwość  daje nam dostęp do pól i metod publicznych naszego dynamicznego komponentu. Dzięki temu możemy łatwo przekazać property name do dynamicznego komponentu z komponentu rodzica lub zawołać metodę:

Jak już się dowiedzieliśmy, nasze dynamiczne komponenty są trzymane w kontenerze viewContainerRef, na konkretnych pozycjach. Możemy łatwo odczytać taki indeks. Klasa ComponentRef, udostępnia nam także property hostView, które reprezentuje konkretne viewRef w Host View. Natomiast serwis ViewContainerRef udostępnia nam metodę indexOf(viewRef), która zwraca nam indeks przekazanego w parametrze viewRef. Łącząc dostępne informacje, złapiemy indeks dynamicznego komponentu w kontenerze:

Posiadając indeks dynamicznego komponentu, możemy go np usunąć, przesunąć na inne miejsce, lub wywołać metodę tylko na konretnym dynamicznym komponecie.Polecam się zapoznać z całym api ViewContainerRef, ponieważ udostępnia wiele ciekawych metod, które pozwolą nam manipulować elementami, które przetrzymuje. Poniżej live example podsumowujący cały artykuł.

LIVE EXAMPLE

… i ostatnia rzecz, bez której nasz kod nie ruszy.

Z racji, że dynamiczny komponent pojawia się w locie, musimy go zadeklarować jako entry component w ngModule, a nie wyłącznie w declarations. W pliku module.app.ts musi być:

entryComponents: [DynamicComponent],

Dokumentacja angulara na temat entry Components mówi:

If your app happens to bootstrap or dynamically load a component by type in some other manner, you’ll have to add it to entryComponentsexplicitly

3 Comments

  1. kartofelek007

    Ciekawy artykuł chociaż tak samo jak cała reszta artykułów w necie pokazuje tylko 1 poziomowe tworzenie komponentów.
    Bardzo często zachodzi potrzeba że będzie trzeba zrobić zagnieżdżone komponenty – np. całą dynamiczną sekcję z dynamicznymi komponentami. Co więcej będzie trzeba mieć jakoś do nich dostęp. I co wtedy?

  2. CTomasz

    Trochę to dziwne, nazywamy dynamicznym komponentem (selector: ‚dynamic-component’) coś co wklejamy do innego komponentu?? To raczej nie ten rodzic powinien nazywać się dynamicznym?? a z drugiej strony co to za dynamiczność jeśli w kodzie i tak mamy ‚zafiksowany’ selektor do wyświetlenia. Raczej jest to inny sposó pokazania .

    Jak ja widze dynamiczny komponent?? W parametrze podaje templatke albo konfiguracje co ma się pojawić w

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *