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:
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:
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)
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:
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.