Angular - interfejsy

Stronę tą wyświetlono już: 54 razy

Interfejsy w Angularze umożliwiają wprowadzenie kontroli typów oraz ułatwiają pracę z danymi zapisanymi w formacie JSON, który jak wiadomo jest formatem tekstowym. Każdy interfejs może zawierać jedynie pola wraz z opcjonalnym opisem ich typów. Oto przykładowy prosty interfejs:

Listing 1
  1. export interface Engine {
  2. power: number;
  3. maxNPM: number;
  4. }

Słowo kluczowe export oznacza, że taki interfejs będzie można eksportować w innym pliku projektu.

Jeżeli zachodzi konieczność uniemożliwienia raz ustawionej wartości pola interfejsu można do tego celu wykorzystać słowo kluczowe readonly w następujący sposób:

Listing 2
  1. export interface Engine {
  2. readonly power: number;
  3. readonly maxNPM: number;
  4. }

Po utworzeniu instancji tak zmodyfikowanego interfejsu:

Listing 3
  1. let engine: Engine = { power: 150, maxNPM: 6000 };

Nadpisanie pól typu readonly nie będzie możliwe.

Istnieje też możliwość utworzenia interfejsu, który będzie przyjmował dowolną liczbę pól np. dowolnego typu. Oto przykład takiego interfejsu:

Listing 4
  1. export interface MyHtml{
  2. name: string;
  3. [style: string]: any;
  4. }

W powyższym przykładzie interfejs ma jedno obowiązkowe pole name oraz może przyjmować dowolną liczbę pól typu any. Oto przykład:

Listing 5
  1. let myHtml: MyHtml = { name: 'b', color: 'white' };
  2. console.log(myHtml);

Powyższy kod wyświetli w konsoli następujące dane:

{ name: 'b', color: 'white' }

Jeżeli dany interfejs posiada sporą liczbę pól do wypełnienia, ale niektóre z nich są opcjonalne, to można je takimi uczynić przy wykorzystaniu znaku ? w następujący sposób:

Listing 6
  1. export interface CarInterface {
  2. brand: string;
  3. maxSpeed: number;
  4. acceleriation: number;
  5. weight: number;
  6. airConditioning?: any; // opcjonalny argument, który może ale nie musi być ustawiony
  7. }

Można też utworzyć obiekt typu Partial, dzięki czemu wszystkie argumenty będą opcjonalne. Oto przykład:

Listing 7
  1. // standardowe utworzenie obiektu typu CarInterface z pominięciem opcjonalnego argumentu
  2. let car1: CarInterface = { brand: 'Ford Mustang', maxSpeed: 250, acceleriation: 5.7, weight: 1000 };
  3. // utworzenie obiektu typu Partial<CarInterface> z pominięciem kilku nie opcjonalnych w interfejsie CarInterface pól
  4. let car2: Partial<CarInterface> = {brand: 'Ford Mustang' };

Problem może się pojawić, gdy jakaś metoda czy funkcja będzie wymagała pełnego obiektu a będąc w posiadaniu okrojonego obiektu nie jest możliwe w bezpośredni sposób przekazanie takiego niepełnego obiektu. W takim przypadku można pokusić się o wykorzystanie słowa kluczowego as w następujący sposób:

Listing 8
  1. let car3: CarInterface = { brand: 'Ford Mustang', maxSpeed: 250 } as CarInterface
  2. console.log(car3)

Wynik wyświetlony w konsoli:

Object { brand: "Ford Mustang", maxSpeed: 250, acceleriation: 5.7, weight: 1000 }

Interfejsy można w łatwy sposób rozszerzać poprzez wykorzystanie słowa kluczowego extends w następujący sposób:

Listing 9
  1. export interface Engine {
  2. power: number;
  3. maxNPM: number;
  4. }
  5. export interface CarInterface extends Engine {
  6. brand: string;
  7. maxSpeed: number;
  8. acceleriation: number;
  9. weight: number;
  10. turnOnEngine?(): void;
  11. }

Teraz interfejs CarInterface został rozszerzony o pola, które posiada interfejs Engine. Zmiana danych zapisanych w formacie JSON na zmienną typu Engine sprowadza się jedynie do sparsowania tegoż JSON-a:

Listing 10
  1. let engineJson: string = '{ "power": 500, "maxNPM": 7000 }';
  2. let engine: Engine = JSON.parse(engineJson);

Oczywiście dane zawarte w JSON-ie powinny odpowiadać tym z interfejsu. Jeżeli tak nie będzie to niestety ale TypeScript doda pola, które według interfejsu nie powinny istnieć w obiekcie.

W tym przypadku zmienna engine jest zmienną typu Engine, dzięki czemu sam Visual Studio Code będzie podpowiadał pola, jakie ten obiekt posiada.

Interfejsy mogą również zawierać deklaracje funkcji np. w taki oto sposób:

Listing 11
  1. export interface Engine {
  2. power: number;
  3. maxNPM: number;
  4. }
  5. export interface CarInterface extends Engine {
  6. brand: string;
  7. maxSpeed: number;
  8. acceleriation: number;
  9. weight: number;
  10. turnOnEngine(): void;
  11. }

Interfejsy jednak nie umożliwiają tworzenie pełnych deklaracji funkcji ani też nie pozwalają na inicjalizację pól interfejsu danymi. Z kolei inicjalizacja zmiennych za pomocą JSON-a ogranicza się tylko do wartości liczbowych, tekstowych, tablicowych lub obiektów, których pola składają się z takich właśnie elementów. Dzieje się tak, ponieważ JSON jest przystosowany jedynie do przechowywania danych nie zaś deklaracji funkcji. W celu ustawienia zmiennej typu funkcyjnego konieczne jest ręczne przypisanie tejże funkcji. Oto przykład:

Listing 12
  1. let carJson: string = '{ "power": 500, "maxNPM": 7000, "brand": "Ford Mustang", "maxSpeed": 250, "acceleriation": 7.5, "weight": 1000 }';
  2. let car: CarInterface = JSON.parse(carJson);
  3. // Inicjalizacja pola funkcji zmiennej car
  4. car.turnOnEngine = () => { console.log('Engiine is turn on') };
  5. // Uruchomienie funkcji
  6. car.turnOnEngine();
  7. // Wyświetlenie obiektu funkcji
  8. console.log('Car', car);

Interfejsy można wykorzystywać do wymuszania obsługi określonych metod przez klasy. Taki mechanizm wykorzystują Angular-owe haki. Oto prosty przykład użycia takiego mechanizmu:

Listing 13
  1. export interface CarI {
  2. turnOnEngine(): void;
  3. }
  4. export class Car implements CarI {
  5. turnOnEngine(): void {
  6. console.log('Engine is running now');
  7. }
  8. }

W momencie, gdy klasa implementuje interfejs, wszystkie pola oraz deklaracje metod muszą być utworzone również i w klasie. Wymusza to również obsługę wszystkich funkcji w deklaracji klasy.

Co ciekawe można również rozszerzać interfejs za pomocą klasy. W takim przypadku interfejs automatycznie zyskuje wszystkie pola i metody klasy bazowej. Oto przykład:

Listing 14
  1. export class Square {
  2. a: number;
  3. constructor(a: number) {
  4. this.a = a;
  5. }
  6. get p(): number {
  7. return this.a * Math.sqrt(2);
  8. }
  9. set p(pValue: number) {
  10. this.a = pValue / Math.sqrt(2);
  11. }
  12. get area(): number {
  13. return this.a * this.a;
  14. }
  15. set area(areaValue: number) {
  16. this.a = Math.sqrt(areaValue);
  17. }
  18. print() {
  19. console.log('Cechy obiektu prostokąta:');
  20. console.log('Długość boku a: ' + this.a);
  21. console.log('Długość przekątnej b: ' + this.p);
  22. console.log('Pole powierzchni Ppow: ' + this.area);
  23. }
  24. }
  25. export interface SquareInterface extends Square {
  26. }

Interfejs SquareInterface został rozszerzony o pola klasy Square przez co automatycznie dysponuje on wszystkimi polami, jakie klasa rozszeżająca dysponowała.

Komentarze