Tworzysz własny komponent? chciałbyś umożliwić użytkownikom definiować globalne ustawienia, które zostaną zaaplikowane na każdej instancji komponentu?
Jeśli tak, to ten wpis jest dla Ciebie 😉 Poczuj moc InjectionTokens!
InjectionToken
Rozważmy następujący przykład:
- tworzymy komponent, np. FileUploadera (o selektorze <file-uploader></file-uploader>), którego chcemy udostępnić społeczności
- konfiguracja uploadera jest oparta o domyślny obiekt konfiguracyjny
- umożliwiamy użytkownikowi zmodyfikować domyślną konfigurację, poprzez przekazanie własnej konfiguracji poprzez dekorator @Input() – <file-uploader [config]=”config”></file-uploader>.
Niby wygodnie…ale jeśli użytkownik ma w aplikacji 100 instancji uploadera, to musi 100 razy przekazać inputa z konfiguracją. Co prawda ciężko mi sobie wyobrazić apkę, gdzie mamy 100 file uploaderów, ale np. kilkaset mat-inputów z Angular Material… jak najbardziej!
Czyż nie byłoby pięknie, jakby użytkownik mógł raz, globalnie skonfigurować swój komponent i nie bawić się w żadne @Input ? No pewnie!
Z pomocą przychodzi klasa InjectionToken:
https://angular.io/api/core/InjectionToken
Dzięki InjectionToken, możemy np. do komponentu dostarczyć obiekt konfiguracyjny, który zostanie zdefiniowany wyłącznie raz, np. w AppModule.
Angular ma bardzo przemyślaną implementację Dependency Injection, z której korzystamy na co dzień tworząc aplikacje angularowe, chociażby w momencie wstrzykiwania zależności do konstruktora klasy komponentu. Dzięki tokenom (tokenem może być np. klasa lub string), Angular potrafi rozróżniać zależności.
FileUploaderComponent
Załóżmy zatem, że tworzymy taki FileUploaderComponent i chcemy umożliwić użytkownikowi dostarczyć globalny obiekt konfiguracyjny.
Rozpoczynamy od stworzenia własnego tokena, który jest instancją klasy InjectionToken i jest eksportowany pod zmienną o ustalonej przez nas nazwie (tu: FILE_UPLOADER_GLOBAL_CONFIG):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { InjectionToken } from '@angular/core'; import { HttpHeaders } from "@angular/common/http"; export const FILE_UPLOADER_GLOBAL_CONFIG = new InjectionToken<FileUploaderConfig>('file-uploader-global-config'); export interface FileUploaderConfig { maxFileSize: number; url: string; auto: boolean; multiple: boolean; headers: HttpHeaders; } |
Tworzymy również interfejs, który opisze nam możliwą konfigurację.
Teraz pozostaje nam dostarczyć nasz token jako zależność w komponencie. Do dekoratora @Inject, musimy przekazać nazwę naszego tokena (i oczywiście go zaimportować):
1 2 3 4 5 6 7 8 |
export class FileUploaderComponent { config: FileUploaderConfig = { ... }; constructor( @Optional() @Inject(FILE_UPLOADER_GLOBAL_CONFIG) globalConfig: FileUploaderConfig) { this.config = globalConfig ? Object.assign(this.config, globalConfig) : this.config; }; } |
I korzystając np. z Object.assign(), łączymy domyślną konfigurację z tą przekazaną globalnie od użytkownika.
Warto również skorzystać z dekoratora @Optional. W przypadku, gdy w konstruktorze oczekujemy dostarczenia danej zależności a nie zostanie ona odnaleziona, to otrzymamy errora (i słusznie). Dzięki @Optional, wskazujemy, że dana zależność jest opcjonalna i nie wydarzy się nic złego, jeśli jej nie dostarczymy.
Jeszcze jeden krok do sukcesu, tym razem po stronie użytkownika, my swoją robotę wykonaliśmy 🙂
Aby użytkownik mógł skorzystać ze swojego obiektu konfiguracyjnego, który jest zależnością komponentu FileUploaderComponent, to musi go dostarczyć na poziomie np. głównego modułu i zarejestrować go w tablicy providers:
1 2 3 4 5 6 7 8 9 |
@NgModule({ declarations: [...], imports: [..], providers: [ {provide: FILE_UPLOADER_GLOBAL_CONFIG, useValue: { multiple: false }} ], bootstrap: [...] }) export class AppModule {} |
Czyli dostarczyliśmy zależność FILE_UPLOADER_GLOBAL_CONFIG, która po wstrzyknięciu będzie obiektem { multiple: false }.
Jeśli jest dla Ciebie nowością pole useValue, to już wyjaśniam! useValue, po prostu podmieni nam zależność na przypisaną wartość. Oprócz useValue, istnieje również możliwość skorzystania w innych przypadkach np. z useClass, które jest bardzo przydatne podczas mockowania serwisów w testach.
Link do przykładu:
https://stackblitz.com/edit/angular-h5qadz?file=app/file-uploader-global-config.token.ts
Tyle na dziś, miłego! 🙂
—
ps! Partner społeczny bloga, szkolenia Angular In Space, wkrótce (21-23 marca), zawita do Gdańska. Jeśli czujesz potrzebę doszlifowania swoich umiejętności z Angulara, lub chcesz wysłać swojego pracownika / pracowników na profesjonalne szkolenie, to jest to idealna okazja. Więcej informacji:
https://angular-in-space.pl/rejestracja/2018-03-21
…a z hasłem angularlove przy zapisach, 10% żniżki! ; )
Mam pytanie… jeśli mam 1 moduł a w nim w kilku miejscach wstrzyknięty komponent który jako input przyjmuje config gdyż w zależności od use case’a renderuje różny content, czy taki przypadek nadaje się na injection token? jesli tak to w jaki sposób nadpisywać config zdefiniowany w app.module ?
jeśli dobrze Cię zrozumiałem, to w Twoim przypadku użycie @Input jest bardziej odpowiednie. Injection Token będzie dobry jeśli ta konfiguracja jest dostarczona poprzez konstruktor. W takiej sytuacji dostarczasz konfigurację jako token w tablicy providers.