Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 3401 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:

export interface Engine { power: number; maxNPM: number; }

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:

export interface Engine { readonly power: number; readonly maxNPM: number; }

Po utworzeniu instancji tak zmodyfikowanego interfejsu:

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:

export interface MyHtml{ name: string; [style: string]: any; }

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:

let myHtml: MyHtml = { name: 'b', color: 'white' }; 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:

export interface CarInterface { brand: string; maxSpeed: number; acceleriation: number; weight: number; airConditioning?: any; // opcjonalny argument, który może ale nie musi być ustawiony }

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

// standardowe utworzenie obiektu typu CarInterface z pominięciem opcjonalnego argumentu let car1: CarInterface = { brand: 'Ford Mustang', maxSpeed: 250, acceleriation: 5.7, weight: 1000 }; // utworzenie obiektu typu Partial<CarInterface> z pominięciem kilku nie opcjonalnych w interfejsie CarInterface pól 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:

let car3: CarInterface = { brand: 'Ford Mustang', maxSpeed: 250 } as CarInterface 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:

export interface Engine { power: number; maxNPM: number; } export interface CarInterface extends Engine { brand: string; maxSpeed: number; acceleriation: number; weight: number; turnOnEngine?(): void; }

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:

let engineJson: string = '{ "power": 500, "maxNPM": 7000 }'; 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:

export interface Engine { power: number; maxNPM: number; } export interface CarInterface extends Engine { brand: string; maxSpeed: number; acceleriation: number; weight: number; turnOnEngine(): void; }

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:

let carJson: string = '{ "power": 500, "maxNPM": 7000, "brand": "Ford Mustang", "maxSpeed": 250, "acceleriation": 7.5, "weight": 1000 }'; let car: CarInterface = JSON.parse(carJson); // Inicjalizacja pola funkcji zmiennej car car.turnOnEngine = () => { console.log('Engiine is turn on') }; // Uruchomienie funkcji car.turnOnEngine(); // Wyświetlenie obiektu funkcji 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:

export interface CarI { turnOnEngine(): void; } export class Car implements CarI { turnOnEngine(): void { console.log('Engine is running now'); } }

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:

export class Square { a: number; constructor(a: number) { this.a = a; } get p(): number { return this.a * Math.sqrt(2); } set p(pValue: number) { this.a = pValue / Math.sqrt(2); } get area(): number { return this.a * this.a; } set area(areaValue: number) { this.a = Math.sqrt(areaValue); } print() { console.log('Cechy obiektu prostokąta:'); console.log('Długość boku a: ' + this.a); console.log('Długość przekątnej b: ' + this.p); console.log('Pole powierzchni Ppow: ' + this.area); } } export interface SquareInterface extends Square { }

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