30 paź 2024
7 min

Inline templates – dlaczego warto je stosować

Podczas codziennego scrollowania X’a natknąłem się na wątek Younes’a. Zadał tam pytanie czy możliwość importowania Typescriptowych constów i enumów skłoniłoby osoby do używania inline template’ów, bez konieczności przypisywania ich do klasy komponentu.


Cóż, w zasadzie to i tak już uwielbiam korzystać z inline template’ów. Okazuje się jednak, że nie każdy napawa do nich takim samym optymizmem co ja. Stąd ten artykuł – przejrzymy jakie potencjalne korzyści możemy zyskać, a także jak poprawić produktywność poprzez używanie wbudowanych narzędzi oferowanych przez IDE.
Jeżeli jeszcze nie obserwujecie Younes’a, gorąco zachęcam!

Template file i inline template

Zanim zaczniemy, zdefiniujmy sobie dostępne opcje, jakie daje nam Angular do generowania komponentów. Dzięki CLI, generowanie komponentów jest bajecznie proste – używamy tylko jednej komendy i magicznie pojawia się cały kod. Domyślnie CLI generuje 4 pliki:

  • Plik .ts do trzymania logiki
  • Plik .html z widokiem naszego komponentu
  • Plik .css/.scss do stylowania
  • Plik .spec.ts do testów
    Mamy jednak pełną kontrolę nad tym co i jak będzie generowane, przez dodanie odpowiednich flag do komendy CLI. Przykładowo, możemy ustawić domyślną strategie change detection, pominąć generowanie testów, ustawić selektor lub przestawić źródło naszego widoku na inline template.
    Inline template to rodzaj definiowania naszego widoku (domyślnie w pliku .html), który osadzamy w pliku Typescript naszego komponentu, za pomocą właściwości template. Mamy więc dwie możliwości co do tworzenia szablonów – albo trzymając go w wewnętrznym pliku:
// hello.component.ts
@Component({  
  standalone: true,  
  selector: 'app-hello',  
  templateUrl: './hello.component.html',  
})  
export class HelloComponent {}
// hello.component.html
<div>Hello world!</div>

albo pisząc go jako string w pliku .ts:

// hello.component.ts
@Component({  
  standalone: true,  
  selector: 'app-hello',  
  template: `<div>Hello world!</div>`,  
})  
export class HelloComponent {}

Domyślna wartości dla flagi --inline-template ustawiona jest na false, dlatego CLI dla komponentów będzie generowało oddzielny plik .html. Jest to coś do czego my Angular deweloperzy jesteśmy przyzwyczajeni – do trzymania widoku w zewnętrznym pliku. Wydaje się to być dobre, właściwie i bardzo angularowe, prawda? Oczywiście, ale chciałbym ugryźć ten temat z troche innej strony i sprawdzić jakie potencjalne usprawnienia może nieść za sobą inline template.

„Zła praktyka”

Podczas poszukiwania opinii na główny temat artykułu, znalazłem wątki na Reddicie i Stackoverflow gdzie sugerowano, że inline template’y to zła praktyka. W mojej ocenie nie jest to prawda. W zasadzie, to inline template’y mogą zachęcić deweloperów do pisania czystszego i lepszego kodu.

Uproszczenie komponentów z Inline Template’ami

Inline template’y niosą za sobą szereg benefitów, które często są pomijane w dyskusjach. Dzięki umieszczeniu zarówno logiki, jak i szablonu w jednym miejscu, inline template’y zapewniają natychmiastowy kontekst i jasność, co ułatwia zrozumienie i utrzymanie komponentów.
Chociaż duże inline template’y mogą rozbudować plik TypeScript, korzystanie z mniejszych, bardziej skoncentrowanych komponentów łagodzi ten problem, czyniąc kod bardziej modułowym i łatwiejszym w utrzymaniu, bez utraty przejrzystości.
W poniższym przykładzie pokażę, jak efektywne użycie inline template’ów może uprościć komponenty i poprawić przepływ pracy, jednocześnie promując czysty i zorganizowany kod.

Przykład

Jako przykład, w naszym repozytorium angular.love znalazłem komponent header, który miał 80 linii kodu w pliku .html. Umieszczenie tego kodu w inline template powoduje, że plik .ts ma 128 linii kodu, z czego 63% to kod HTML. Trochę za dużo, prawda?
Zamiast mieć jeden duży plik header.component.ts, możemy podzielić go na mniejsze części:

  • header.component.ts
  • header-logo.component.ts
  • header-language.component.ts
  • header-mobile-menu.component.ts
  • header-hamburger.component.ts

Efekt końcowy wygląda następująco:

<header class="bg-al-background/95 z-30 h-20 w-full border-b shadow-xl">  
  <div  
    class="mx-auto flex h-full w-full max-w-screen-xl items-center justify-between px-6 py-4 xl:px-0"  
  >  
    <al-header-logo />  

    <div class="flex flex-row items-center">  
      <al-navigation class="hidden lg:block" />  

      <al-header-language  
        [language]="language()"  
        (languageChange)="languageChange.emit($event)"  
      />  

      <ng-content />  

      <al-header-hamburger  
        [isOpened]="showNav()"  
        (toggleOpen)="toggleNav()"  
      />  
    </div>  
  </div>  
</header>  
<al-header-mobile-menu [isOpened]="showNav()" (closed)="toggleNav()" />

Jeżeli chcecie zobaczyć jak wyglądał komponent przed tą zmianą, możecie zobaczyć pełny kod w tym PR https://github.com/HouseOfAngular/angular-love/pull/339/files. Jest on trochę za długi, żeby go tuta umieszczać.

Z 80 LOC naszego template’u zjechaliśmy do 24 linii, co w rezultacie przekłada się na 67 LOC header’a. Oczywiście – pozostały kod nie wyparował. Cały czas istnieje, tylko w innych komponentach. Po prostu rozdystrybuowaliśmy jego pojedyncze części, tym samym otrzymując komponent z bardziej deskryptywnym kodem.

Wnioski

Szczerze mówiąc, na pierwszy rzut oka ten refactor niewiele zmienia. Na stronie wygląda tak samo, działa tak samo, a skończyło się to większą ilością plików do utrzymania. Ale to nie o to chodzi – ten przykład pokazuje, jak drastycznie zmienia się perspektywa, korzystając z inline template’ów. Jestem pewien, że ten komponent wyglądałby dokładnie tak, gdybyśmy użyli inline template’a od samego początku istnienia komponentu. Oczywiście, moglibyśmy dojść do podobnych wniosków, trzymając szablon w zewnętrznym pliku, ale inline template daje nam natychmiastowy feedback w miarę rozrastania się komponentu.

Można też powiedzieć, że celem komponentów jest tworzenie jednostek kodu wielokrotnego użytku. Powyższe komponenty zdecydowanie nie są wielokrotnego użytku, prawda? Ale nie uważam, żebyśmy musieli patrzeć na to w ten sposób. Komponenty nie powinny być oceniane tylko pod kątem ich reużywalności – istnieją inne rzeczy, które warto wziąć pod uwagę. Oto lista potencjalnych korzyści wynikających z tworzenia mniejszych komponentów:

  • Lepsze rozdzielenie odpowiedzialności – każdy komponent ma jedną, jasno określoną rolę, co zmniejsza złożoność i ułatwia naprawy oraz wprowadzanie zmian.
  • Łatwiejsze ponowne wykorzystanie – nawet jeśli teraz nie planujesz ponownego użycia, mniejsze komponenty mogą okazać się przydatne w przyszłości, czy to do wielokrotnego użytku, czy do testów.
  • Wyższy poziom abstrakcji – mniejsze komponenty pozwalają ukryć szczegóły implementacji, dzięki czemu szablon komponentu nadrzędnego jest bardziej przejrzysty i łatwiejszy do zrozumienia.
  • Zmniejszenie obciążenia kontekstem – dzielenie komponentów na mniejsze, bardziej skoncentrowane części zmniejsza wysiłek potrzebny do ich zrozumienia i utrzymania. Dzięki temu łatwiej jest zachować porządek i lepiej się czujesz, nie będąc przytłoczonym zbyt skomplikowanymi komponentami.

Code Review

Trzymanie szablonów w osobnych plikach zmusza do tworzenia w głowie mapy powiązań między logiką komponentu a jego szablonem. To może utrudniać śledzenie całego działania komponentu podczas code review, z uwagi na ciągłe przełączanie się między plikami, w celu ch zrozumienia. Kiedy szablon jest obok logiki w jednym miejscu, łatwiej ogarnąć całość, co sprawia, że review staje się bardziej intuicyjne i efektywne. Można więc powiedzieć, że inline template’y ułatwiają code review i przyspieszają wprowadzanie zmian.

Co z boilerplatem?

Kiedy przechodzimy na inny model myślowy, ostatecznie tworzymy więcej komponentów niż zwykle. Ale to w sumie nie problem, mamy przecież CLI, prawda? Cóż, nie będę kłamać – jestem za leniwy na korzystanie z CLI, nawet jeśli to tylko jedno kliknięcie. Na szczęście nowoczesne IDE oferują świetne funkcje, które pozwalają szybko generować dowolny kawałek kodu. Na przykład w WebStorm mamy coś takiego jak Live Templates, gdzie po wpisaniu konkretnego skrótu IDE automatycznie wstawia gotowy fragment kodu. Mamy mnóstwo wbudowanych skrótów, takich jak a-component czy a-component-inline. Są super, ale potrzebowałem czegoś bardziej skrojonego pode mnie. Ostatecznie zrobiłem sobie własny szablon.

Użytkownicy VSCode mogą zainstalować rozszerzenie Angular Snippets stworzonego przez Johna Pape https://marketplace.visualstudio.com/items?itemName=johnpapa.Angular2.
Jeżeli chcemy je trochę bardziej zmodyfikować pod siebie, zawsze możemy stworzyć własne!

{
   "Angular Standalone Component":{
      "prefix":"comp",
      "body":[
         "import { ChangeDetectionStrategy, Component } from '@angular/core';",
         "",
         "@Component({",
         "\tstandalone: true,",
         "\tselector: '${2:app}${3:${1/[A-Z]/-${0:/downcase}/g}}',",
         "\ttemplate: `$0`,",
         "\tchangeDetection: ChangeDetectionStrategy.OnPush,",
         "})",
         "export class ${1:Name}Component {}"
      ],
      "description":"Creates an Angular standalone component"
   }
}

To na przykład, automatycznie konwertuje nazwę komponentu na jego selektor w wersji rozdzielonej myślnikami, pisany małymi literami.

Czy inline template’y to zawsze dobre rozwiązanie?

Niekoniecznie. Inline template’y mają swoje zalety, ale nie zawsze są najlepszym rozwiązaniem. Czasami szablony mogą się rozrosnąć, zwłaszcza przy skomplikowanych formularzach. Dodatkowy kod potrzebny do obsługi Value Accessora może sprawić, że dzielenie szablonu na mniejsze części będzie po prostu niepraktyczne. W takich przypadkach trzymanie szablonu w osobnym pliku może ułatwić zrozumienie kontekstu i utrzymanie kodu.

Jednak, w wielu sytuacjach, szczególnie przy mniejszych komponentach, inline template’y mogą znacząco poprawić czytelność i szybkość pracy, dając natychmiastowy kontekst bez potrzeby przeskakiwania między plikami. Bezpośrednie połączenie logiki i szablonu może usprawnić rozwój, szczególnie w prostszych komponentach, gdzie trzymanie wszystkiego w jednym miejscu zwiększa efektywność.

Czemu nie oba?

Nie ma jednej słusznej drogi. Nic nie stoi na przeszkodzie, żeby mieszać inline template’y z zewnętrznymi szablonami, w zależności od sytuacji. Wybór między nimi powinien być osobistą decyzją, opartą na twoich preferencjach lub doświadczeniu. Ostatecznie chodzi o to, żeby wybrać podejście, które najlepiej odpowiada potrzebom twojego projektu i sprawia, że proces tworzenia jest bardziej efektywny.

Złoty środek

Skąd więc wiedzieć, kiedy użyć inline template, a kiedy osobnego pliku z szablonem? Czy istnieje jasny moment, w którym inline template jest lepszym wyborem? Czy jest jakiś limit linii kodu dla inline template’ów? Czy w ogóle są one potrzebne? I czy mieszanie obu podejść nie spowoduje bałaganu w kodzie? To są pytania, na które ty i twój zespół musicie odpowiedzieć sami, biorąc pod uwagę potrzeby waszego projektu.

Osobiście polecam spróbować inline template’ów wszędzie tam, gdzie to możliwe. Warto zacząć tworzenie komponentów z inline template’em od samego początku. Trzymanie szablonu wewnątrz komponentu jest w porządku, o ile kontekst pozwala łatwo zrozumieć jego przepływ i logikę.

Podsumowanie

W tym artykule omówiliśmy plusy i minusy korzystania z inline template’ów w Angularze. Chociaż inline template’y nie zawsze są najlepszym wyborem dla skomplikowanych lub dużych komponentów, oferują świetne korzyści pod względem czytelności i efektywności w przypadku mniejszych, bardziej skoncentrowanych komponentów. Rozmawialiśmy o tym, jak rozbijanie komponentów i trzymanie szablonów inline może uprościć rozwój, usprawnić code review i poprawić zrozumienie kontekstu. Ostatecznie wybór między inline template’ami a zewnętrznymi plikami zależy od potrzeb twojego projektu, ale warto dać inline templates szansę, szczególnie w przypadku mniejszych komponentów. Kluczowe jest znalezienie balansu, który będzie działać dla ciebie i twojego zespołu.

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.