Co jeśli zostajemy przydzieleni do projektu, który bardzo dobrze funkcjonuje na produkcji, ale technologia, która za nim stoi jest uznawana za “przestarzałą” i chcemy ją wymienić? Czasami można porzucić stary projekt i zacząć tworzyć w jego miejsce nowy. Jeśli projekt jest duży, a firma na nim zarabia, może to się okazać niemożliwe.
Na ratunek przychodzi wzorzec “dusiciela” (strangler pattern).
Polega on na przebudowie starego systemu od środka, zastępując i dodając w nim funkcjonalności w innej technologii zamiast dotychczasowej. Z czasem liczba takich “nowych” funkcjonalności rośnie, a my możemy ograniczyć do minimum stary kod, zachowując ciągłe działanie systemu. Z takim wyzwaniem zmierzyłem się przy budowie aplikacji administracyjnej w firmie Unilink, a w poniższym artykule opowiem w jaki sposób udało mi się zaimplementować mikro frontendy w Angularze.
Przygotowanie
Pierwszym etapem implementacji nowej części systemu było stworzenie kilku nowych widoków dla dotychczasowej aplikacji. Do rozwiązania tego zadania zdecydowałem się na użycie mikro frontów z wykorzystaniem Web Components. Wszystkie nowe widoki stanowiły oddzielne mikro aplikacje, ale korzystały z podobnego kodu, dlatego były rozwijane wewnątrz jednego repozytorium, tak aby móc skorzystać z szybkiego tworzenia i modyfikowania wspólnych komponentów. Docelowo, po osiągnięciu odpowiedniej liczby funkcjonalności w Angularze, miała powstać oddzielna aplikacja typu SPA.
Do stworzenia mono repozytorium skorzystałem z narzędzia Nx. Dodatkowo, w kontekście Angulara, rozbudowuje ono CLI o dodatkowe możliwości i lekko zmienia zestaw domyślnych ustawień nowo utworzonego projektu Angularowego.
Kilka zauważalnych różnic w nowym projekcie utworzonym w Nx względem nowego projektu utworzonego przy pomocy oryginalnego CLI:
- dodany jest eslint do statycznej analizy kodu
- dodany jest prettier do formatowania kodu
- zamiast Jasmine i Karma do testów jednostkowych dodany jest Jest
- dodany jest cypress do wykonywania testów e2e
Istnieje również możliwość utworzenia projektu bez powyższych zależności.
W podejściu monorepo, kod wielu różnych bibliotek i projektów przechowuje się w jednym repozytorium. Nx pozwala na skorzystanie z dwóch rodzajów monorepozytorium – zintegrowanego (integrated monorepo) i opartego na paczkach (package based monorepo) – które można ze sobą łączyć. Zintegrowane skupia się na wydajności i łatwości utrzymania. Repozytorium oparte na paczkach daje większą elastyczność i łatwość użycia.
Nx dodaje bardzo ważną komendę do CLI – affected, która pozwala wylistować wszystkie biblioteki i projekty wewnątrz monorepoztorium, które zostały dotknięte wprowadzonymi zmianami w porównaniu do poprzedniego lub wskazanego miejsca w historii gita. Na każdym elemencie tej listy można wykonać dowolne zadania, np. lint, testy, build itd. Dodatkowo Nx przechowuje w pamięci wykonane już polecenia, a także pozwala na synchronizację pamięci podręcznej między wieloma stacjami roboczymi (Nx Cloud).
Web Components pozwala na utworzenie własnego znacznika HTML. Taki znacznik może zawierać całą (prawie) aplikację w Angularze. Do tego służy właśnie biblioteka Angular Elements. Każdy widok (feature), można zamknąć w oddzielnej mikro aplikacji. Angular w Web Components ma jednak duże ograniczenie – nie posiada wsparcia dla RouterModule. W przypadku nawigacji między ekranami aplikacji trzeba posiłkować się innymi rozwiązaniami. Więcej o Angular Elements znajdziecie w tym artykule.
Podział aplikacji na biblioteki
Nx posiada wiele różnych “presetów” umożliwiających wygenerowanie innej struktury folderów. Używając presetu dla Angulara w projekcie pojawią się dwa główne foldery: apps i libs.
Apps – zawiera wszystkie aplikacje, które można zbudować/zaserwować. Ich konfiguracje są zawarte w zmodyfikowanym pliku angular.json, który importuje je z plików project.json z poszczególnych aplikacji. Mogą to być także aplikacje nie napisane w Angularze.
Libs – zawiera biblioteki, czyli moduły z których będą korzystały aplikacje. Ich definicje również trafiają do pliku angular.json, a sama konfiguracja znajduje się w pliku project.json w folderze danej biblioteki. Biblioteka nie jest pełnoprawną aplikacją. Możemy ją zbudować i opublikować w dowolnym repozytorium, np. NPM lub prywatnym Verdaccio lub wykorzystać bezpośrednio w kodzie. Nx pozwala na bardzo wygodne importowanie plików z bibliotek przez uproszczone ścieżki (ang. aliasy), których konfigurację można znaleźć w pliku tsconfig.base.json.
Większość logiki naszej aplikacji powinna znaleźć się właśnie w folderze libs. W ten sposób aplikacje możemy składać z odpowiednich modułów w bibliotekach.
Składanie aplikacji z takich modułów jest bardzo przydatne jeśli chcemy wykorzystać daną funkcjonalność w więcej niż jednej aplikacji. Cała Funkcjonalność jest zamknięta w jednej bibliotece. Następnie trafia ona do systemu legacy w jednej aplikacji – Web Componencie. Obok może powstać druga aplikacja, która z wykorzystaniem routingu wyświetli dokładnie ten sam widok razem z innymi funkcjonalnościami – bibliotekami.
Podział aplikacji na domeny
Jak to wszystko razem spiąć i nie zwariować?
Aby zachować porządek, biblioteki możemy podzielić na różne obszary biznesowe, a także na różne warstwy. Taki obszar biznesowy możemy określić domeną. Mogą to być np. szkolenia, płatności, zamówienia, biblioteka wspólnych elementów itd. Każda z tych domen może zostać dodatkowo podzielona na dowolne warstwy. Możemy wykorzystać podział zaproponowany przez Manfreda Steiera w artykule Tactical Domain-Driven Design with Angular and Monorepos
- Api
- Feature
- UI
- Domena
- Util
Jeśli chcesz dowiedzieć się więcej o podziale na warstwy i sposobie na utrzymanie porządku w kodzie polecam zapoznać się z dokumentacją Nx, artykułem w tej tematyce z oficjalnego bloga Nx oraz serią artykułów Manfreda. W tym artykule pokażę Ci jak wykorzystać jedną funkcjonalność w dwóch różnych aplikacjach i nie będę wchodził w detale podziału na domeny i warstwy.
Dodanie pierwszej mikroaplikacji
Dodajemy workspace Nx
1 |
npx create-nx-workspace@latest |
Interaktywny kreator zada nam kilka pytań:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
✔ Workspace name (e.g., org name) · new-system ? What to create in the new workspace … apps [an empty workspace with no plugins with a layout that works best for building apps] npm [an empty workspace with no plugins set up to publish npm packages (similar to yarn workspaces)] ts [an empty workspace with the JS/TS plugin preinstalled] react [a workspace with a single React application] angular [a workspace with a single Angular application] next.js [a workspace with a single Next.js application] nest [a workspace with a single Nest application] express [a workspace with a single Express application] web components [a workspace with a single app built using web components] react-native [a workspace with a single React Native application] react-express [a workspace with a full stack application (React + Express)] angular-nest [a workspace with a full stack application (Angular + Nest)] |
Wybieramy angulara jako framework i konfigurację dla naszego projektu.
1 2 |
✔ Application name · invoices ✔ Default stylesheet format · scss |
Ostatnie z pytań w interaktywnym kreatorze nowego workspace będzie brzmiało:
1 |
Set up distributed caching using Nx Cloud (It's free and doesn't require registration.) |
Nx oferuje możliwość skorzystania z cache wykonanych poleceń (lint, testy, build) w chmurze, co poprawia wydajność skryptów, które w przypadku braku zmian w kodzie skorzystają z wcześniej zbudowanych elementów.
Nasza pierwsza aplikacja została dodana. Stanie się ona z automatu domyślną aplikacją uruchamianą po wywołaniu komendy npm start
bez dodatkowych parametrów. Jeśli chcemy zmienić domyślną aplikację, należy zmienić jej nazwę w pliku nx.json.
1 |
"defaultProject": "invoices" |
W przypadku omawianego projektu, każdy ekran (widok) aplikacji powinien być oddzielną biblioteką. Dodajmy zatem nowy widok – bibliotekę, która trafi do mikroaplikacji – Web Componentu.
Dodanie biblioteki feature
Wcześniej została dodana już aplikacja, która będzie kontenerem dla naszego widoku. Dodajmy teraz bibliotekę, która będzie zawierała właściwy widok.
1 |
nx generate lib feature-invoices –directory billing |
Zostaniemy zapytani jakiego generatora chcemy użyć.
1 2 3 4 5 |
? Which generator would you like to use? … @nrwl/angular:library @nrwl/workspace:library None of the above |
Wybieramy @nrwl/angular:library
Do folderu libs zostanie dodany folder billing, który zawiera folder z nowo utworzoną biblioteką. Folder billing to w tym wypadku konwencja utrzymania bibliotek w obrębie jednej domeny biznesowej. Może to być dowolna nazwa, która reprezentuje jakiś obszar biznesowy. Użyta nazwa feature również jest konwencją, która może przydać się przy dodaniu podziału aplikacji na warstwy, aby móc je z łatwością rozpoznać w strukturze projektu.
Plik na który warto zwrócić uwagę to index.ts, który eksportuje wybrane elementy z biblioteki, dając do nich dostęp z zewnątrz, spoza biblioteki.
Do biblioteki dodajmy teraz Angularowy komponent, który będzie naszym widokiem. Możemy skorzystać z CLI lub dowolnego pluginu do generowania komponentów, np. Nx Console.
Pamiętaj, żeby po dodaniu nowego komponentu, w konfiguracji dodawanego modułu dodać komponent w tablicy exports
. Dzięki temu, będziemy mogli skorzystać z tego komponentu po zaimportowaniu modułu BillingFeatureInvoicesModule.
1 2 3 4 5 6 |
@NgModule({ imports: [CommonModule], declarations: [InvoicesComponent], exports: [InvoicesComponent], }) export class BillingFeatureInvoicesModule {} |
Możemy teraz zaimportować moduł z biblioteki we wcześniej utworzonej aplikacji. Otwórz app.module.ts w aplikacji invoices i zaimportuj feature module.
Po wpisaniu nazwy modułu w tablicy importów i próbie automatycznego zaimportowania modułu, VS Code zasugeruje nam dwie ścieżki do modułu. Jeśli wybierzemy absolutną ścieżkę (2 pozycja), IDE od razu podświetli ją na czerwono. To eslint i reguła wprowadzona przez Nx, która chroni między innymi przed takim rodzajem importów. Wybieramy w takim razie preferowaną – pierwszą – opcję, czyli import ze ścieżką ukrytą za aliasem, zgodnym ze standardem npm.
1 |
import { BillingFeatureInvoicesModule } from '@new-system/billing/feature-invoices'; |
Jeśli chcesz sprawdzić wszystkie dostępne ścieżki do bibliotek, są one skonfigurowane w pliku nx.json w głównym folderze projektu.
Dodajemy jeszcze wywołanie komponentu w szablonie app.component.html
1 |
<new-system-invoices></new-system-invoices> |
Zmiana nazwy znacznika
Wygenerowany znacznik dla komponentu Invoices nie do końca oddaje z jakim rodzajem komponentu mamy do czynienia – czy jest to feature czy coś innego. W pliku zawierającym InvoicesComponent zmieńmy nazwę selectora HTML na f
eature-invoices.
1 2 3 4 5 6 7 |
import {Component } from '@angular/core'; @Component({ selector: 'billing-feature-invoices', templateUrl: './invoices.component.html', }) export class InvoicesComponent {} |
IDE pokaże nam ostrzeżenie z eslinta:
1 |
The selector should start with one of these prefixes: "new-system" (https://angular.io/guide/styleguide#style-02-07)eslint@angular-eslint/component-selector |
Musimy zmienić prefix dla selektorów w obrębie tej biblioteki. Wchodzimy do pliku .eslintrc.json w folderze libs/billing/feature-invoices i zmieniamy konfigurację na:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "extends": ["../../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts"], "extends": [ "plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates" ], "rules": { "@angular-eslint/directive-selector": [ "error", { "type": "attribute", "prefix": "feature", "style": "camelCase" } ], "@angular-eslint/component-selector": [ "error", { "type": "element", "prefix": "feature", "style": "kebab-case" } ] } }, { "files": ["*.html"], "extends": ["plugin:@nrwl/nx/angular-template"], "rules": {} } ] } |
Z takim prefiksem coś jest jednak nie tak. Każdy komponent zadeklarowany w tej bibliotece będzie musiał mieć prefiks feature
, co nie będzie zgodne z prawdą. Możemy przyjąć np. prefiks zgodny z nazwą domeny, z której pochodzi feature, np. billing
. Zmieniamy w takim razie ponownie konfigurację, aktualizujemy selektor naszego komponentu i zmieniamy szablon w naszej mikro aplikacji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "extends": ["../../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts"], "extends": [ "plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates" ], "rules": { "@angular-eslint/directive-selector": [ "error", { "type": "attribute", "prefix": "billing", "style": "camelCase" } ], "@angular-eslint/component-selector": [ "error", { "type": "element", "prefix": "billing", "style": "kebab-case" } ] } }, { "files": ["*.html"], "extends": ["plugin:@nrwl/nx/angular-template"], "rules": {} } ] } |
1 2 3 4 5 6 7 8 |
import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'billing-feature-invoices', templateUrl: './invoices.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class InvoicesComponent {} |
1 |
<billing-feature-invoices></billing-feature-invoices> |
Zbudowanie mikroaplikacji do Web Componentu
Aplikacja ma jedynie importować feature module i zapewniać niezbędną konfigurację. Budowanie do Web Componentu będzie wymagało dodania kilku zmian do domyślnie wygenerowanych plików. Skorzystamy z instrukcji z artykułu Angular Elements na blogu.
Dodajemy dwie niezbędne biblioteki:
1 |
ng add @angular/elements |
1 2 |
Ng add is not natively supported by Nx Instead, we recommend running `npm install @angular/elements && npx nx g @angular/elements:ng-add` |
Nx dekoruje CLI Angulara dodając funkcjonalności pod maską, ale jak widać nie wszystkie. Użyjmy więc proponowanego skryptu:
1 2 |
npm install @angular/elements npm i @webcomponents/webcomponentsjs // krótsza wersja npm install |
I wprowadzamy zmiany w module i komponencie z aplikacji invoices.
apps/invoices/src/app/app.module.ts
1 2 3 4 5 6 7 8 9 10 11 |
@NgModule({ declarations: [AppComponent], imports: [BrowserModule, BillingFeatureInvoicesModule], bootstrap: [AppComponent], }) export class AppModule { constructor(injector: Injector) { const el = createCustomElement(InvoicesComponent, { injector: injector }); customElements.define('new-system-invoices', el); } } |
apps/invoices/src/app/app.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'new-system-root', template: `<div id="container"></div>`, styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnInit { ngOnInit() { document.querySelector('#container')!.innerHTML = '<new-system-invoices></new-system-invoices>'; } } |
Budowanie Web Componentu
Serwujemy aplikację, żeby sprawdzić czy wszystko działa:
1 |
npm start invoices |
Wszystko działa. Schody zaczynają się jeśli chcemy mieć ciastko i zjeść ciastko, czyli zbudować aplikację invoices jako Web Component i nadal być w stanie ją zaserwować.
Zgodnie z artykułem, aby umożliwić budowanie mikroaplikacji, usuwamy konfigurację bootstrap
z dekoratora AppModule.
1 2 3 4 5 6 7 8 9 10 11 12 |
@NgModule({ declarations: [AppComponent], imports: [BrowserModule, BillingFeatureInvoicesModule], }) export class AppModule { constructor(injector: Injector) { const el = createCustomElement(InvoicesComponent, { injector: injector }); customElements.define('new-system-invoices', el); } ngDoBootstrap() {} } |
Z naszego localhost:4200 zniknęła zawartość aplikacji. Naprawimy to.
Możemy przyjąć, że będziemy budować aplikację do Web Componentu tylko w konfiguracji produkcyjnej, a w przeciwnym wypadku skorzystamy z bootstrapowania aplikacji. Skorzystajmy więc z pola production
z pliku environment.ts
w aplikacji.
Hook ngDoBootstrap
w module jest zawsze wywoływany, nawet jeśli aplikacja zostanie zbudowana do Web Componentu, dlatego przeniesiemy tam logikę, która zadecyduje w jakim trybie uruchomić aplikację.
Modyfikujemy trochę app.module i kod głównego komponentu app.component.ts:
apps/invoices/src/app/app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
const APPLICATION_TAG = 'new-system-invoices'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, BillingFeatureInvoicesModule], }) export class AppModule implements DoBootstrap { constructor( private _injector: Injector, private _applicationRef: ApplicationRef ) {} ngDoBootstrap(): void { if (environment.production) { try { // prevent 'has already been defined as a custom element' error if (customElements.get(APPLICATION_TAG)) { return; } // create custom elements from angular components const el = createCustomElement(AppComponent, { injector: this._injector, }); // define in browser registry customElements.define(APPLICATION_TAG, el); } catch (err) { console.error(err); } } else { this._applicationRef.bootstrap(AppComponent); } } } |
apps/invoices/src/app/app.component.ts
1 2 3 4 5 6 |
@Component({ selector: 'new-system-root', template: `<new-system-invoices></new-system-invoices>`, styleUrls: ['./app.component.scss'], }) export class AppComponent {} |
Teraz po zapisaniu plików powinniśmy zobaczyć, że nasza aplikacja działa.
Co się dzieje w ngDoBootstrap?
Sprawdzamy czy flaga production w pliku environment jest ustawiona na true. Następnie sprawdzamy, czy w Web Componentach nie został już zadeklarowany inny komponent pod tym samym znacznikiem HTML. Może to być przydatne, jeśli używamy tego samego komponentu w więcej niż jednym miejscu w statycznym pliku HTML. Następnie tworzymy Angular Element przekazując do niego niezbędne argumenty i deklarujemy Web Component pod podanym znacznikiem. Jeśli flaga production jest jednak ustawiona na false, bootstrapujemy komponent AppComponent do Angularowej aplikacji.
Sprawdźmy czy uda się uruchomić aplikację jako Web Component. Zgodnie z artykułem Łukasza wywołujemy komendę budowania i połączenia plików w jeden
1 2 |
ng build --prod --output-hashing=none cat dist/apps/invoices/runtime.js dist/apps/invoices/polyfills.js dist/apps/invoices/main.js > dist/apps/invoices.js |
i wywołujemy Web Component w statycznym pliku HTML:
1 2 3 4 5 |
<body> <script src="../dist/apps/invoices.js"></script> <h1>Static</h1> <new-system-invoices></new-system-invoices> </body> |
Więcej o Angular Elements możesz przeczytać we wspomnianym wcześniej artykule na blogu.
Wykorzystanie nowego modułu feature w aplikacji SPA
Wygenerowanie kolejnej aplikacji do workspace w Nx jest dostępne pod komendą:
1 |
nx generate app application-with-routing |
Na pytanie kreatorze w CLI wpisujemy w konsoli y
:
1 |
✔ Would you like to configure routing for this application? (y/N) · true |
W folderze apps mamy teraz dwie aplikacje. Możemy zaserwować z tego samego workspace jednocześnie drugą aplikację (i wiele innych). Wystarczy wywołać skrypt
1 |
npm start application-with-routing – –port 4201 –o |
Ponieważ port 4200 jest już zajęty, możemy do komendy przekazać ręcznie port na którym chcemy wystawić naszą aplikację. Możemy też pozostawić CLI wykrycie innego dostępnego portu nie przekazując tego parametru, ale wtedy dostaniemy za każdym razem inny port.
Dodajmy do głównego app.module.ts nowej aplikacji feature, ale tym razem przez routing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, RouterModule.forRoot( [ { path: '', component: InvoicesComponent, }, ], { initialNavigation: 'enabledBlocking' } ), ], providers: [], bootstrap: [AppComponent], }) export class AppModule {} |
Przy próbie wygenerowania ścieżki do importu, IDE nie podpowie tym razem prawidłowej ścieżki. Należy pamiętać, że wszystkie pliki, które mają być eksportowane z biblioteki na zewnątrz, muszą zostać dodane do pliku index.ts w odpowiadającej bibliotece.
libs/billing/feature-invoices/src/index.ts
1 2 |
export * from './lib/billing-feature-invoices.module'; export { InvoicesComponent } from './lib/invoices/invoices.component'; |
Importujemy teraz komponent w app.module.ts. Przykład takiej konfiguracji routingu jest bardzo podstawowy. Moglibyśmy skorzystać tu z dodatkowo utworzonej biblioteki – Shell – która zawierałaby potrzebną konfigurację routingu dla całej domeny billing. Jeśli chcesz dowiedzieć się jak to zrobić, daj znać w komentarzu.
Teraz po otworzeniu adresu localhost z wybranym portem powinniśmy zobaczyć ten sam feature, który mamy w mikro aplikacji serwowanej na porcie 4200.
Plusy i minusy Web Componentów
Wykorzystanie mikro frontów w formie Web Componentów ma jasne i ciemne strony. Zacznijmy od minusów.
Minusy
- rozmiary paczek – mimo optymalizacji, nadal trudno będzie osiągnąć małe rozmiary zbudowanych mikro aplikacji (jednym ze sposobów na złagodzenie negatywnych skutków jest lazy loading web-componentów),
- brak routingu – w kontekście Web Componentu nie możemy skorzystać z router-outletów, ponieważ nasza aplikacja nie podpina się pod adresy przeglądarki. Taka mikro aplikacja jest zależna od nawigacji rodzica. Możemy oczywiście korzystać ze standardowych anchor tagów zamiast routerLinków,
- z czasem może być potrzebny DevOps – podział na wiele bibliotek powoduje wydłużenie czasu przechodzenia procesu budowania. Lintowanie, testowanie i budowanie całego projektu może wydłużyć się do kilkudziesięciu minut (jeśli nie godzin). Monorepo będzie potrzebowało wprowadzenia kilku optymalizacji w procesie CI/CD, np. z wykorzystaniem komendy
affected
i cache’owania.
Plusy
- dostajemy możliwość stopniowego tworzenia nowych funkcjonalności systemu, przenosząc dotychczasowy legacy system jedynie w tryb utrzymania,
- w przypadku korzystania z sesji w cookie, Web Component nie musi posiadać własnej autoryzacji. Wszystkie akcje wykonywane są w kontekście rodzica, czyli systemu w którym są osadzone nasze Web Componenty. To rodzic “użycza” sesji,
- migracja do aplikacji SPA z systemu Legacy jest stosunkowo prosta, jeśli pokrycie funkcjonalności systemu będzie wystarczająco duże. Musimy dodać oddzielną aplikację – ramkę, która będzie zarządzała routingiem, autoryzacją i innymi detalami, którymi Web Componenty nie musiały się przejmować.
Podsumowanie
Architektura mikro frontendów może być przydatna nie tylko w dużych, rozproszonych zespołach. Można skorzystać z niej także w przypadku stopniowego przebudowywania już istniejącego systemu. Podział aplikacji na moduły sprawi, że nasz kod stanie się łatwiejszy w utrzymaniu, ale należy pamiętać, że nie jest to rozwiązanie, które magicznie sprawi, że modyfikowanie kodu stanie się prostsze. Jeśli będziemy dbali o dobre praktyki, dodawanie nowych funkcjonalności stanie się szybsze przez zwiększone reużycie istniejącego kodu między kolejnymi funkcjonalnościami / projektami.
P.S. A propos dużych rozmiarów paczek z Web Componentami. Aktualnie nasza mikro aplikacja, która wyświetla jedynie tekst invoices works!
waży aż 226 kB. To stanowczo za dużo! Jeśli potrzebujesz czegoś mniejszego, warto rozważyć stworzenie web componentów bez użycia frameworków. Z takim podejściem będzie można się spotkać np. w Google Material v3.
Napisałeś, że minusem jest brak routingu – nie jest to prawda. Routing jest możliwy wystarczy, że zamiast RouterModule użyjemy RouterTestingModule, który wykorzystywany jest do testowania – nie ingeruje w przeglądarkę. Wszystkie ustawienia routingu używamy tak jak wcześniej tylko adres nie będzie się zmieniać.
Hej, zaintrygowałeś mnie. Mógłbyś podać fragment implementacji? Z tego co próbowałem tego podejścia nie działa, więc nadal trzeba statycznie podać komponent na sztywno (można się było spodziewać 😉 ). routerLink i routerLinkActive też nie. Jaka jest w takim razie korzyść z importowania RouterTestingModule zamiast niedziałającego RouterModule? Wygląda na to, że w obu przypadkach potrzebna jest customowa implementacja jeśli nie chcemy użyć hrefa.