Dependency injection z dziedziczeniem w Aurelii

W trakcie przebudowywania systemu (dla zapewnienia jego największej elastyczności) stwierdziłem, że najlepszym rozwiązaniem, będzie stworzenie subitemów, każdego typu, jako custom elementów. Refaktoring zajmie sporo dłużej, ale przynajmniej wszystko będzie “tak jak trzeba” :)

Każdy typ subitemów w systemie ma prostą rolę — musi zapewnić interakcję pomiędzy użytkownikiem, a systemem, przy modyfikacji danych. Najprostszy z nich jest zwykłym inputem, większy edytorem WYSWIG, a największy edytorem zdjęć z możliwością przycinania ich w wyskakującym oknie. Dzięki rozwiązaniu z custom elementami użytkownicy CMSa będą mogli dopisać własny, zapewniający niestandardowe funkcje — jak na przykład zaawansowany color-picker lub date-picker.

Aby ujednolicić wszystkie elementy muszą one jednak współdzielić pewien interfejs do komunikacji z resztą systemu (np. do informowania systemu o wprowadzonych zmianach przez użytkownika). Jako, że w Aurelii każdy custom element jest parą view i viewmodel, który jest JSową klasą od razu nasuwa się na myśl dziedziczenie.

Jednakże do komunikacji z serwisami działającymi w aplikacji cały czas jako jedna instancja (serwis do wymiany danych, info o zalogowanym użytkowniku) używa się dependency injection (DI), aby wskazać naszej klasie, że zależy od tych serwisów i wstrzyknąć ich istniejącą instancję lub stworzyć ją. W Aurelii DI jest realizowane za pomocą zaimportowania zależności i wstrzyknięcia ich przez dekorator @inject() . Jeśli jednak wstrzykiwać zależność w każdym z naszych custom elementów za każdym razem powtarzalibyśmy kod realizujący import oraz DI, i to dla każdej z zależności! Na szczęście da się z tym uporać w naprawdę łatwy sposób.

Przyjmijmy, że mamy klasę Subitem (taką jak w systemie — będącą rodzicem wszystkich innych typów subitwmów), która zależy od EventAggregator’a, ponieważ przez niego będzie publikować eventy, dotyczące wprowadzonych zmian wartości.

import {
inject
} from ‘aurelia-framework’;
import {
EventAggregator
} from ‘aurelia-event-aggregator’;
@inject(EventAggregator)
export class Subitem {
constructor(ea) {
this.ea = ea; // EventAggregator
}
 publishChange(subitemName, newVal) {
this.ea.publish(‘subitemValueChange’, {
subitemName: subitemName,
newVal: newVal
});
}
}

Klasa ma jedna metodę — służącą właśnie do publikowania eventu. Jak użyć jej w klasie dziedziczącej? Pierwsze co przychodzi na myśl to:

import {
Subitem
} from ‘subitem.js’;
export class ChildSubitem extends Subitem {
...
publish(a, b){
this.publishChange(a, b);
}
}

Niestety otrzymamy błąd — niezdefiniowany this.ea! Ale jak to przecież definiowaliśmy go w klasie Subitem? Otóż DI nie przechodzi “samo” na klasy, które z dziedziczą z klasy, do której nastąpiło wstrzyknięcie!

Na szczęście jest na to rozwiązanie! Wystarczy użyć spread syntax i rest parameters. Kod będzie wyglądać tak:

import {
Subitem
} from “subitem.js”
export class ChildSubitem extends Subitem {
constructor(…rest) {
super(…rest);
}
...
publish(a, b){
this.publishChange(a, b);
}
}

Co w przypadku gdy w klasie dziedziczącej chcemy wstrzyknąć dodatkowe zależności? Robimy tak:

import {
inject
} from ‘aurelia-framework’;
import {
Subitem
} from “subitem.js”
import {
OtherDependency
} from “otherDependency.js”
@inject(OtherDependency)
export class ChildSubitem extends Subitem {
constructor(otherDependency, …rest) {
super(…rest);
}
...
publish(a, b){
this.publishChange(a, b);
}
}

I wszystko pięknie działa!

Show your support

Clapping shows how much you appreciated Mateusz Choma’s story.