Möchte man heutzutage eine Webseite entwickeln, kommt man um die drei großen JavaScript-Frameworks React, Vue und Angular nicht herum. Viele Faktoren entscheiden hierbei über die Wahl des Frameworks. Neben den offensichtlichen Faktoren Dokumentation, Verbreitung und Features ist ebenso wichtig, dass der Einstieg in die Entwicklung mit dem Framework leicht und schnell zu schaffen ist. Gerade die Höhe der Einstiegshürde wurde an Angular oft kritisiert.
ZUM NEWSLETTER
Regelmäßig News zur Konferenz und der .NET-Community
So verlangt Angular beispielsweise die Implementierung eines Angular-Moduls. Innerhalb eines Angular-Moduls werden Angular-Bausteine (z. B. Komponenten) deklariert oder es werden andere Module importiert, deren Funktionalität benötigt wird. Am meisten für Verwirrung sorgt hierbei die Unterscheidung zwischen Angular- und ECMAScript-Modul. Gerade Anfänger:innen, die soeben erst Grundkenntnisse in JavaScript bzw. TypeScript erlangt haben, ist meist nicht klar, wozu ein Angular-Modul benötigt wird.
Selbst langjährige Angular-Entwickler:innen wünschen sich immer wieder einen Weg, die Verwendung von Angular-Modulen optional zu gestalten. Zu viel Boilerplate-Code und die Verleitung zur Entwicklung von zu großen sogenannten Shared-Modulen sind hierbei zwei der Hauptgründe dafür, dass sich die Entwicklung mit Angular-Modulen oft schwierig gestaltet.
Abhilfe hat man sich hierbei durch die Implementierung des Single-Component-Angular-Module- oder auch SCAM-Patterns geschaffen. Dabei wird jede einzelne Komponente, Direktive oder Angular Pipe in einem einzigen alleinstehenden Angular-Modul deklariert. Importiert werden lediglich die Abhängigkeiten, die die einzelne Komponente, Pipe oder Direktive benötigt (Abb. 1).
Abb. 1: SCAM-Pattern: Ein Modul deklariert nur eine einzige Komponente
Dadurch können größere Shared-Module aufgespalten und die Abhängigkeiten innerhalb der eigenen Featuremodule weitaus feingranularer importiert werden (Abb. 2).
Abb. 2: Aktuelle Angular-Version 15.0.0-rc4
So ist beispielsweise die Angular-Material-Bibliothek [1] eine der bekanntesten Umsetzungen dieses Patterns. Für jede UI-Komponente gibt es ein eigenes Modul: MatButtonModul, MatDatePickerModule oder MatTableModule, um nur einige Beispiele zu nennen.
Schnell fragt man sich, warum der Mehraufwand eines Moduls überhaupt notwendig ist, und so sah man mit großer Freude, dass im Oktober 2021 ein RFC [2] des Angular-Teams veröffentlicht wurde, in dem es hieß, dass Angular-Module mit Hilfe des sogenannten Standalone API von nun an optional sein sollen.
Das Angular-Team erwähnte selbst, dass die verpflichtende Nutzung der Angular-Module oftmals zu Problemen bei der Featureentwicklung des Angular-Frameworks beitrug. Doch warum gab es Angular-Module dann überhaupt? Wie sieht die Angular-Entwicklung nun ohne Angular-Module aus und was bedeutet das für eine bestehende Architektur?
Genau diese Fragen möchte ich hier beantworten. Nachdem wir uns mit dem neuen Standalone API vertraut gemacht haben, werden wir tiefer einsteigen in die neue Architektur und in die Entwicklung mit Standalone Components.
Hinweis: Der Text arbeitet mit Angular Version 15.0.0-rc.4. Bis zur Veröffentlichung ist Angular Version 15 bereits erschienen. Daher könnte es kleinere Abweichungen innerhalb der gezeigten Codebeispiele geben.
Warum gibt es überhaupt Angular-Module?
Die Antwort auf diese Frage führt uns zu den Wurzeln von Angular: 2010 wurde AngularJS erstmals releast. Zu dieser Zeit gab es keinerlei Möglichkeit, Code, der zusammengehörte, zu gruppieren. Das Gruppieren aber führt nicht nur zu einer besseren Wartbarkeit des Codes, sondern ermöglicht, dass Funktionalität, die an mehreren Stellen benötigt wird, wiederverwendet werden kann. Für Letzteres benötigt der Compiler einen Kontext, um die Sichtbarkeit der einzelnen Codebausteine zu erkennen. Da es zu dieser Zeit noch kein eigenes Modulkonzept in JavaScript gab, wurden in AngularJS hierfür Angular-Module eingeführt (Listing 1).
Listing 1
angular.module('myModule', []).
value('a', 123).
factory('a', function() { return 123; }).
directive('directiveName', ...).
filter('filterName', ...);
Innerhalb eines Angular-Moduls deklarierte man unter anderem Direktiven, Controller oder Services. Mit der Veröffentlichung von Angular im Jahre 2016 wurden zwar die meisten Bausteine des Frameworks neu geschrieben, allerdings wurden Teile des alten Compilers übernommen. Das führte dazu, dass Angular weiterhin darauf angewiesen war, mit Angular-Modulen zu arbeiten (Listing 2).
Listing 2 @NgModule({ declarations: [AppComponent], imports: [BrowserModule], providers: [], bootstrap: [AppComponent] }) export class AppModule {}
Tatsächlich war die Angular-Community nie wirklich glücklich mit dieser Entscheidung und auch aus dem Angular-Team hörte man immer wieder Stimmen der Unzufriedenheit. Denn mit der Einführung von ECMAScript 2015 wurde nun das Konzept der ECMAScript-Module [3] vorgestellt. Das heißt, JavaScript hatte von nun an einen gewissermaßen natürlichen Weg, um Code zu modularisieren.
SIE WOLLEN MEHR INPUT ZUM THEMA WEB DEVELOPMENT?
Entdecken Sie den BASTA! Web Development Track
Die Kombination aus beiden Modulkonzepten in einem Framework verwirrte besonders Neueinsteiger:innen und so arbeitete das Angular-Team an der Entwicklung eines neuen Compilers: des Ivy-Compilers. Der bereits in Angular Version 8 eingeführte Compiler ermöglichte nun, dass jede Komponente ihren ganz eigenen Compilerkontext hatte. Ein Angular-Modul wird also nicht mehr benötigt. Der Rückwärtskompatibilität wegen musste das Angular-Team aber noch lange Zeit mit dem älteren Compiler, der View Engine, arbeiten und war vorerst weiterhin auf die Nutzung der Angular-Module angewiesen. Doch mit dem Entfernen der View Engine seit Angular 13 war es endlich so weit: Angular-Module waren für die Entwicklung mit Angular nicht mehr notwendig und das neue Standalone API wurde als sogenannte Developer Preview [4] in Version 14 vorgestellt.
Standalone Components
Das darunterliegende Konzept ist relativ einfach und schnell erklärt: Von nun an werden sämtliche Abhängigkeiten, die eine Komponente benötigt, direkt in das neue imports-Property innerhalb des Component-Decorators geschrieben (Listing 3).
Listing 3
@Component({
selector: 'app-root',
standalone: true,
template: '<h1 *ngIf="title">{{ title }}</h1>>',
imports: [ NgIf ],
styleUrls: [ './app.component.scss' ],
})
export class AppComponent {...}
Darüber hinaus wird noch ein neues Property standalone gesetzt, um dem Compiler klarzumachen, dass es sich hierbei um eine Standalone Component handelt.
Was uns ebenso auffällt, ist, dass für die Verwendung der NgIf-Direktive nicht mehr das gesamte Common Module importiert werden muss, sondern lediglich die strukturelle Direktive selbst.
Das Angular-Team hat zusätzlich daran gearbeitet, die Bausteine ihrer eigenen Module als Standalone zur Verfügung zu stellen. Sie können dann, wie in Listing 3 gezeigt, innerhalb des imports-Property eingefügt werden. So gibt es beispielsweise die DatePipe, AsyncPipe, NgForOf oder das bereits gezeigte NgIf aus dem Common Module als Standalone-Variante, aber auch das für die Navigation benötigte RouterOutlet oder die RouterLink-Direktive aus dem Router Module. Doch was ist, wenn wir eine weitere Angular-Komponente erstellen möchten und sie in unsere AppComponent einfügen wollen? Normalerweise konnten wir die Komponente innerhalb desselben Angular-Moduls deklarieren, in dem die AppComponent deklariert war. So wurde sichergestellt, dass die neue Komponente für die AppComponent sichtbar ist. Mit Standalone Components sieht das jetzt natürlich ein wenig anders aus.
Zuerst legen wir eine neue Komponente an:
> ng generate component header –standalone
Setzen wir das neue –standalone-Flag, wird eine Komponente generiert, die bereits das standalone-Property gesetzt hat.
Fügen wir die HeaderComponent nun in unsere AppComponent ein, erhalten wir vorerst die uns vertraute Fehlermeldung aus Abbildung 3.
Abb. 3: Die AppComponent kennt noch nicht die HeaderComponent
Interessanterweise wird uns allerdings vorgeschlagen, die HeaderComponent zu importieren. Tun wir das, so können wir sehen, dass die HeaderComponent nun ebenfalls innerhalb des imports-Arrays der AppComponent eingefügt wurde (Listing 4).
Listing 4
@Component({
selector: 'app-root',
standalone: true,
template: '<h1 *ngIf="title">{{ title }}</h1>>',
imports: [ NgIf, HeaderComponent ],
styleUrls: [ './app.component.scss' ],
})
export class AppComponent {...}
Daraus folgt, dass Standalone Components sich ähnlich wie Angular-Module verhalten. Benötige ich eine Standalone Component in einer anderen Komponente, muss ich sie lediglich importieren. Voraussetzung ist hierbei, dass beide Komponenten das standalone-Property besitzen.
Wir können darüber hinaus nicht nur Komponenten als standalone deklarieren: Der Pipe- und der Directive-Decorator besitzen ebenfalls die neue standalone-Property.
Die Vorstellung, alle abhängigen Komponenten, Pipes, Direktiven etc. immer wieder in unsere Standalone Component zu importieren, klingt zunächst anstrengend. Zwar müssen wir das zurzeit noch manuell mit ein wenig Hilfe unserer Entwicklungsumgebung erledigen, doch ist geplant, dass in naher Zukunft anhand eines Autoimports zu lösen – ähnlich wie bereits TypeScript-Module in unsere Angular-Anwendung automatisch importiert werden, sobald sie in einem Code genutzt werden sollen.
Da wir aus unserer AppComponent eine Standalone Component kreiert haben, stellt sich uns nun die Frage, wie unsere gesamte Applikation eigentlich gebootstrappt wird, da wir das AppModule nicht mehr benötigen.
Bootstrapping einer Angular-Applikation
Bisher benötigte Angular immer ein sogenanntes AppModule, anhand dessen die gesamte Applikation mit Hilfe der bootstrapModule-Funktion gebootstrappt wurde. Innerhalb des AppModules definierten wir darüber hinaus unsere AppComponent als sogenannte Root-Komponente (Listing 5).
Listing 5
//main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Listing 6
//main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component;
bootstrapApplication(AppComponent)
.catch(err => console.error(err));
Mit dem neuen Standalone API können wir eine einzelne Komponente bootstrappen (Listing 6). Dafür stellt uns Angular seit Version 14 die bootstrapApplication-Funktion zur Verfügung. Dort übergeben wir nur noch unsere Root-Komponente, die allerdings bereits als Standalone Component vorliegen muss.
Wie wir sehen, wird das AppModule auch hier nicht mehr benötigt. Möchten wir allerdings Services für die gesamte Applikation providen, stand uns hierfür immer das providers-Array innerhalb des NgModules-Decorators zur Verfügung. Gewiss sind treeshakeable Providers – das heißt, der Service provided sich selbst – immer zu bevorzugen, allerdings benötigen wir oftmals komplexere Dependency-Injection-Logik, die wir dann innerhalb des providers-Arrays klarer definieren können. Ein einfaches Beispiel hierfür sind Injection Tokens wie der APP_INITIALIZER. Aber auch hierfür hat das neue Standalone API eine Lösung: Sämtliche Provider werden von nun an innerhalb der bootstrapApplication-Funktion deklariert.
Neben der Root Component wird eine sogenannte ApplicationConfig mitübergeben. Dort können wir – mit kleinen Anpassungen – weiterhin das bekannte providers-Array definieren, um unserer Applikation Injection Tokens und Services zur Verfügung zu stellen.
Neben unseren eigenen Services wollen wir oftmals andere Module importieren, die dann ihrerseits Services anbieten. Einige der bekanntesten Vertreter sind hierbei das RouterModule, HttpClientModule oder auch das StoreModule aus der Redux-Bibliothek NgRx. Mit Hilfe der neuen importProvidersFrom-Funktion werden diese Services uns weiterhin zur Verfügung gestellt, indem wir einen oder mehrere ModuleWithProviders als Parameter übergeben (Listing 7).
Listing 7
//main.ts
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(StoreModule.forRoot({...})),
{
provide: Window,
useValue: window,
},
],
})
Das Beste daran ist, dass hierbei wirklich nur die Provider des jeweiligen Moduls importiert werden. Übergibt man beispielsweise das RouterModule, wird nicht der gesamte Modulcode mit allen Direktiven importiert, sondern eben nur Services, zum Beispiel der RouterService.
Routing und Lazy Loading einer Angular-Applikation
Das nächste große Thema, mit dem wir uns beschäftigen müssen, ist die neue Art und Weise, wie wir unsere Routen innerhalb der Applikation registrieren. Bisher haben wir uns hierfür ein AppRoutingModule angelegt und die Routenkonfiguration innerhalb der forRoot-Funktion des Routing-Moduls übergeben.
Listing 8
//routes.ts
export const APP_ROUTES: Routes = [
{
path: '',
redirectTo: '/books',
pathMatch: 'full',
},
{
path: 'books',
component: BookComponent,
},
]
Das Konzept mit dem Standalone API ist tatsächlich sehr ähnlich. Zuerst definieren wir unsere Routen in einer separaten Datei, ohne jedoch hierfür ein Extramodul anzulegen (Listing 8). Hierbei exportieren wir lediglich die APP_ROUTES-Konstante. Für die Registrierung dieser Routen bietet uns das Standalone API eine provideRouter-Funktion an. Diese müssen wir innerhalb des neuen providers-Arrays in unserer main.ts aufrufen (Listing 9).
Listing 9
// main.ts
bootstrapApplication(AppComponent, {
providers: [
...
provideRouter(APP_ROUTES),
]
}
Die provideRouter-Funktion ist hierbei lediglich eine spezielle Variante der importsProvidersFrom-Funktion. Tatsächlich ist es sogar so, dass in Version 14 die Routen noch wie folgt registriert werden mussten:
importProvidersFrom(RouterModule.forRoot(APP_ROUTES))
Während das eine sehr generelle Art und Weise ist, Services bereitzustellen, ist es denkbar, dass in Zukunft mehrere solcher spezieller Provider-Funktionen zur Verfügung stehen. StoreModule.forRoot({}) könnte beispielsweise in Zukunft lediglich eine provideStore-Funktion sein. Auch für den HttpClient gibt es bereits eine eigene Variante, dazu aber später mehr.
Wie bereits erwähnt, werden an dieser Stelle lediglich die Services des Moduls importiert. Um die Navigation vollständig zu implementieren, wird noch das RouterOutlet benötigt sowie die RouterLink-Direktive. Auch sie werden als Standalone-Bausteine angeboten, sodass wir sie lediglich innerhalb der AppComponent oder jeder anderen Komponente, in der wir die Direktiven nutzen wollen, importieren (Listing 10).
Listing 10
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet,
RouterLink,
],
...
})
export class AppComponent {}
Nachdem wir verstanden haben, wie wir mit dem neuen Standalone API Routen registrieren können, stellt sich direkt die Frage nach Lazy Loading. Lazy Loading gilt als eine der Best Practices, wenn es darum geht, die Größe des initialen JavaScript Bundles zu verringern. Hierbei wird die gesamte Applikation in sogenannte Featuremodule aufgespalten, die nur dann nachgeladen werden, wenn sie benötigt werden, also wenn der Nutzer in eine Ansicht wechselt, die den Code eines Featuremoduls zur Anzeige benötigt. Alle Komponenten, die wir dazu brauchen, sind in diesem Featuremodul deklariert. Wie funktioniert nun also das Prinzip des Lazy Loading ohne Module?
Prämisse ist wie immer, dass sämtliche Komponenten, die als Bundle dynamisch nachgeladen werden sollen, als Standalone Components vorliegen. Zuerst legen wir eine Konfigurationsdatei an, in der wir die Kindrouten definieren (Listing 11). Diese exportieren wir ebenfalls. Der Trick ist nun, dass wir nur diese Routen lazy loaden anstatt eines ganzen Moduls. Das klingt erst einmal seltsam, aber sehen wir uns das Ganze einmal an (Listing 12).
Listing 11 //books.routes.ts export const BOOK_ROUTES: Routes = [ { path: '', component: BookComponent, }, { path: 'new', component: BookNewComponent, }, { path: ':id', component: BookDetailComponent, }, ] Listing 12 //routes.ts export const APP_ROUTES: Routes = [ … { path: 'books', loadChildren: () => import('./features/book/routes') .then(m => m.BOOK_ROUTES), }, ];
Der Angular-Compiler versteht, dass wir alle Komponenten innerhalb der BOOK_ROUTES gemeinsam als Bundle ausliefern wollen. Sobald der Nutzer nun eine Route besucht, die innerhalb der BOOK_ROUTES definiert ist, wird das gesamte Bundle nachgeladen, wie wir es auch vom Lazy Loading von Modulen kennen (Abb. 4).
Abb. 4: Das BOOK_ROUTES Bundle wird dynamisch nachgeladen
Vielleicht kommt nun die Frage auf, wie wir in Zukunft unsere Ordner innerhalb der Anwendung strukturieren sollen. Das Anlegen eines Moduls hat uns standardmäßig einen Ordner angelegt – dieser fehlt uns jetzt. Dennoch empfehle ich, Komponenten, die zusammengehören beziehungsweise zusammen ausgeliefert werden, innerhalb eines Ordners zu gruppieren (Abb. 5). So behält man weiterhin die Übersicht, welche Komponenten zu einem Feature gehören, auch wenn sie nicht mehr über ein Featuremodul zusammen deklariert werden.
Abb. 5: Ordner für Komponenten-Bundles
Bevor ich zeige, wie wir einzelne Komponenten lazy loaden können, möchte ich noch einmal kurz auf die Services zurückkommen. Wir haben zwar bereits gelernt, wie wir Services für die gesamte Applikation providen können, jedoch nicht, wie wir einen Service nur für die Komponenten eines spezifischen Features bereitstellen. Hier konnten wir immer auf das providers-Array des zugehörigen Featuremoduls zurückgreifen. Dieses providers-Array ist nun Teil der Routenkonfiguration (Abb. 6).
Abb. 6: Das neue Route-Interface
Listing 13
//routes.ts
export const APP_ROUTES: Routes = [
...
{
path: 'books',
providers: [BookApiService]
loadChildren: () => import('./features/book/routes')
.then(m => m.BOOK_ROUTES),
},
];
Soll nun also ein Service nur für die Komponenten der BOOK_ROUTES bereitgestellt werden, kann er direkt innerhalb der Routenkonfiguration an die Route geschrieben werden (Listing 13). Der BookApiService kann daraufhin nur von Komponenten innerhalb der books-Route injiziert werden.
Ein neues und ebenfalls lang ersehntes Feature ist das Lazy Loading einzelner Komponenten. Praktisch war es bereits in vorherigen Angular-Versionen möglich. Die tatsächliche Implementierung gestaltete sich allerdings oftmals sehr umständlich und enthielt viel Boilerplate-Code. Seit Version 14 können wir Komponenten ganz einfach innerhalb der Routenkonfiguration dynamisch nachladen (Listing 14). Wie zu vermuten, wird beim Aktivieren der books-Route die BookComponent dynamisch nachgeladen (Abb. 7).
Listing 14 export const APP_ROUTES: Routes = [ ... { path: 'books', loadComponent: () => import('./features/book/book/book.component') .then(m => m.BookComponent), } ];
Abb. 7: Die BookComponent wird dynamisch nachgeladen
Wie wir gesehen haben, können wir innerhalb unserer Angular-Applikation auch vollständig ohne Angular-Module navigieren. Selbst das Lazy Loading von Bundles ist uns nicht abhandengekommen. Die Funktion provideRouter [5] bietet uns zusätzlich weitere Möglichkeiten zur Routerkonfiguration an. So können wir mit withPreloading(PreloadAllModules) eine Strategie festlegen, wann genau eine Komponente oder ein Bundle von Komponenten dynamisch nachgeladen werden soll. Sämtliche Optionen, die wir bisher innerhalb der forRoot-Funktion gesetzt haben, können von nun an innerhalb withRouterConfig übergeben werden.
Das neue HttpClient API
Als Teil des neuen Standalone API führt Angular 15 eine neue Methode zur Verwendung des HttpClient API ein, die keine HttpClientModule erfordert. Wie bereits erwähnt, erhalten wir auch für das Bereitstellen des HttpClient eine eigene Funktion (Listing 15). Innerhalb der Funktion provideHttpClient können zusätzliche Konfigurationen des HttpClient Service übergeben werden, beispielsweise der Support von JSONP (JSON mit Padding) oder Optionen für Cross-site Request Forgery (XSRF).
Listing 15
// main.ts
import { provideHttpClient, withJsonpSupport } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
…
provideHttpClient(withJsonpSupport()),
],
})
Die Verwendung des HttpClient empfiehlt sich immer, da er uns bei der Kommunikation mit einem HTTP-Backend einiges an Arbeit abnimmt. Er setzt automatisch application.json als MIME-Type und parst die Response-Daten in den angegebenen TypeScript-Datentyp. Darüber hinaus steht uns die Nutzung von sogenannten HTTP-Interceptors zur Verfügung. Wie der Name schon sagt, fangen sie den HTTP Request ab, bevor er an das Backend gesendet wird. Dadurch können wir beispielsweise bei allen HTTP Requests den authorization-Header setzen, ohne es bei allen Service-Aufrufen manuell implementieren zu müssen. Diese Interceptors müssen ebenfalls wieder innerhalb der bootstrapApplication mit provide bereitgestellt werden (Listing 16).
Listing 16
// main.ts
bootstrapApplication(AppComponent, {
providers: [
...
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
provideHttpClient(withInterceptorsFromDi()),
],
})
Angular Inteceptors sind nichts Neues, ihre Registrierung innerhalb des Standalone API allerdings schon. Neben dem Bereitstellen des bereits bekannten Injection Token HTTP_INTERCEPTORS muss ebenfalls die Funktion withInterceptorsFromDi aufgerufen werden. Seit Angular 15 wird die Verwendung von Interceptors durch eine neue Featurefunktion namens withInterceptors() vereinfacht. Sie akzeptiert eine Liste von Interceptor-Funktionen, die wir verwenden möchten (Listing 17). Beide Varianten können auch gleichzeitig genutzt werden; wie zuvor ist lediglich die Reihenfolge der Registrierung der Interceptors wichtig.
Listing 17
// main.ts
bootstrapApplication(AppComponent, {
providers: [
…
provideHttpClient(withInterceptors([
(req, next) => {
return next(req);
},
])),
],
})
Sicher ist die Entwicklung einer Angular-Appliktion mit dem neuen Standalone API für langjährige Angular-Entwickler:innen an einigen Stellen gewöhnungsbedürftig. Fängt man ein neues Angular-Projekt an, ist es allerdings sinnvoll, direkt auf Standalone Components zu setzen. Die Größe der Bundles, die an den Webbrowser ausgeliefert werden, kann sich dadurch stark verringern. Als Neueinsteiger:in in die Angular-Welt ist es darüber hinaus bei Weitem einfacher, nicht mehr mit zwei unterschiedlichen Modulkonzepten hantieren zu müssen: Es gibt nur noch ECMAScript-Module.
ZUM NEWSLETTER
Regelmäßig News zur Konferenz und der .NET-Community
Bestehende große Anwendungen auf Standalone Components zu migrieren, mag sehr aufwendig erscheinen. Es empfiehlt sich daher zuerst, mit sämtlichen Bausteinen anzufangen, die wir innerhalb unserer Shared-Module deklariert haben. Ebenso lohnt es sich, simple UI-Komponenten bereits auf Standalone zu migrieren, also solche, die selbst keine weiteren Kindkomponenten oder größere Abhängigkeiten haben – traditionelle Dumb Components.
Ich hoffe, ich konnte Ihnen einen guten Einblick in die Entwicklung mit dem neuen Standalone API geben. Ein vollständiges Beispiel einer Standalone-Angular-Anwendung kann in meinem GitHub-Account eingesehen werden [6].