Odpowiedzialny programista tworząc aplikacje przestrzega powszechnie uznanych zasad tworzenia oprogramowania. Jedną z takich zasad jest Single Responsibility Principle, która uczy nas, że każdy moduł powinien mieć jedno, jasno zdefiniowane zadanie. Przenosząc tę zasadę na strukturę aplikacji powinniśmy, co najmniej, wydzielić każdą tworzoną klasę do osobnego pliku. O ile świat JavaScript'u próbował wielokrotnie zmierzyć się z problemem modularyzacji aplikacji, to jednak dopiero standard ES6 pozwolił w pełni zaadaptować takie podejście.

Modularyzacja w ES6

W ramach standardu ES6 wprowadzono pojęcie modułu oraz standardowego sposobu wyrażania zależności za pomocą polecenia import.

Za moduł przyjmuje się pojedyczny plik. Wszystkie elementy w nim zdefiniowane są jego własnością i nie zabrudzają globalnej przestrzeni aplikacji. Elementy, które chcemy udostępnić innym modułom należy jawnie wskazać przez oznaczenie ich poleceniem export. Wyeksportowane elementy możemy importować i używać w modułach zależnych poleceniem import.

Przykładowo, możemy zdefiniować zależność pomiędzy modułami:

service.ts

export class Service {}

component.ts

import {Service} from './service';
export class Component {
    private service: Service;
}

Problemy z importowaniem zależności

Standardowo importowane moduły są wyszukiwane względem importującego pliku. W przypadku nietrywialnej struktury może to doprowadzić do pogroszenia czytelności aplikacji.

Wyobraźmy sobie strukturę kodu:

src
  \- user
    \- details
      user-details.component.ts
  \- authentication
    \- authentication.service.ts

Wówczas zawartość pliku user-details.component.ts mogłaby wyglądać:

component.ts

import {AuthenticationService} from '../../authentication/authentication.service';
export class UserDetailsService {
    private service: AuthenticationService;
}

Utrzymywanie względnych ścieżek importowanych modułów jest trudne. Czytelność istniejących importów jest wątpliwa, a dopisywanie nowych niewygodne. Rozwiązaniem tego problemu może być stosowanie absolutnych ścieżek do modułów, liczonych względem głównego katalogu ze źródłami. Przy takim podejściu dopisywanie nowych importów jest proste (widząc strukturę aplikacji), a czytając jesteśmy w stanie szybko się zorientować jakie zależności właściwie wciągamy.

Użycie absolutnych ścieżek zależności

Na szczęście zalecane podejście do pisania aplikacji w Angular 2 zakłada stosowanie kompilatora TypeScript, a ten od dłuższego czasu wspiera stosowanie absolutnych ścieżek przy importowaniu zależności. Dodatkowo, również Angular CLI prawidłowo obsluguje bundlowanie zasobów zbudowanych z wykorzystaniem absolutnych ścieżek.

W tym celu należy zmodyfikować plik konfiguracyjny kompilatora TypeScript. W pliku tsconfig.json dopisujemy atrybut baseUrl: "."

{
  "compilerOptions": {
    "baseUrl": "."
  }
}

Od teraz możemy we wszystkich miejscach importować zależności podając ich absolutne ścieżki:

import {AuthenticationService} from 'authentication/authentication.service';
export class UserDetailsService {
    private service: AuthenticationService;
}

Podsumowanie

Wprowdzenie modularyzacji do standardu ES6 to ogromny krok w stronę poprawy jakości aplikacji. Jednak podawanie względnych ścieżek do zależności może szybko doprowadzić do pogorszenia czytelności kodu i przyprawić niejednego programistę o ból głowy. Na szczęścię kompilator TypeScript pozawala definiować zależności wskazując absolutne ścieżki - wystarczy za pomocą atrybutu baseUrl kompilatora zdefiniować katalog, względem którego chcemy wyszukiwać pliki do importowania.