Angular v17 made Standalone the default. Many projects have already switched with ng generate @angular/core:standalone. You no longer need to learn NgModules, and building apps feels more intuitive.
The Standalone migration wave reached Angular libraries, not just individual projects. Ionic Angular joined in v7.5.0. To understand Ionic Angular Standalone, I read through how Ionic Angular works.
Ionic Angular Standalone architecture
To understand Ionic Angular Standalone, you need to understand the old Module-based setup. Starter templates hid most of this, but Ionic Angular initialized through the forRoot method in app.module.ts.
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, IonicModule.forRoot()],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent],
})
export class AppModule {}
It initializes via APP_INITIALIZER. For details, see [Angular] Defining initialization logic with APP_INITIALIZER.
Modules that used Ionic components imported IonicModule, which registered every Ionic component. For example, ion-title registered a component like this.
@ProxyCmp({
inputs: ['color', 'size']
})
@Component({
selector: 'ion-title',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['color', 'size'],
})
export class IonTitle {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
@ProxyComp is a decorator defined by Ionic Angular. In short, it lets you define component methods and attributes. That was one example; IonicModule was the module that registered the full set of such components.
So what changed in Ionic Angular Standalone? Angular now bootstraps the app from main.ts like this (I will not cover the path through app.config.ts here).
bootstrapApplication(AppComponent, {
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
provideIonicAngular(),
provideRouter(routes),
],
});
You can see IonicModule replaced by provideIonicAngular. provideIonicAngular runs Ionic initialization via APP_INITIALIZER, but it does not register components. So how did components change? ion-title now looks like this.
@ProxyCmp({
+ defineCustomElementFn: defineIonTitle,
inputs: ['color', 'size']
})
@Component({
selector: 'ion-title',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['color', 'size'],
+ standalone: true
})
export class IonTitle {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
As expected for Standalone, it has standalone: true. In Ionic Angular Standalone, you must register Ionic components as Standalone components. Obvious in hindsight, but worth confirming. Also, defineCustomElementFn was added. Custom elements used to be defined in IonicModule's APP_INITIALIZER; now each component loads its own.
Bundling IonIcon
When Ionic Angular moved to Standalone, IonIcon was removed from assets. You can see this in the starter template's angular.json.
"assets": [
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
- },
+ }
- {
- "glob": "**/*.svg",
- "input": "node_modules/ionicons/dist/ionicons/svg",
- "output": "./svg"
- }
],
Previously, all IonIcons were copied from node_modules into assets and loaded at display time. In Standalone, the recommended approach is to embed only the IonIcons you need in the bundle with addIcons.
import { addIcons, closeCircleOutline } from 'ionicons';
addIcons({ closeCircleOutline });
addIcons tells the Angular builder which IonIcons to embed in the bundle. If you still copy all icons into assets, you can keep using the old approach.
What this enables
For many developers, "Ionic Angular Standalone" means more boilerplate even though Ionic follows Angular conventions. So what are the benefits?
First, tree-shaking now follows Angular standards (Ionic previously used its own lazy-loading mechanism for components), so bundle size drops for most apps. Dropping the custom path also means fewer library bugs caused by Ionic itself. App load should improve too—two lazy-loading mechanisms became one. (I have not benchmarked load myself; I refer to the Ionic blog post for the performance claim.) Third, and perhaps the best part: Ionic components are now statically analyzable, so you can use Angular's new ESBuild-based build system. When I tried it locally, build speed clearly improved.
Summary
Keeping up with library evolution is often tedious, but skipping it creates future debt. I want to check the details and adopt changes one by one. I plan to write soon about automatically migrating to Ionic Angular Standalone— I hope that helps too.
See you next time.