First Look at Standalone Components: Angular Beyond NgModules
An overview of standalone components in Angular.
The great Angular team has released two RFCs about it already, and with the latest next release of Angular 14, specifically CLI 14.0.0-next.12 and Core 14.0.0-next.15, we get a real look at what is coming next.
When you generate a new project using:
npx @angular/cli@next new ng14
it still feels the same, same interactive questions, the same project structure is created with AppModule
.
But there is a hidden easter egg there, so I kept exploring to see what in the future it can look like.
The first thing is removing AppModule
For the first time ever, I have made my Angular without AppModule
or any NgModule
to that matter but few changes were needed.
First thing is to add a standalone flag in the AppComponent
, then change the main.ts
to initialize the application with AppComponent
only. For that, you need to use the new function bootstrapApplication
. It will look like this:
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';if (environment.production) {
enableProdMode();
}bootstrapApplication(AppComponent)
.catch(err => console.error(err));
But things aren’t that simple, because now it doesn’t know what is router-outlet, but it’s an easy solution to just import RouterModule
inside the AppComponent
, and yes you can do that now.
But a more serious error shows, and not in your terminal but in the browser (runtime not a compile-time error in other words):
ERROR NullInjectorError: R3InjectorError(Standalone[AppComponent])[ChildrenOutletContexts -> ChildrenOutletContexts -> ChildrenOutletContexts]:
NullInjectorError: No provider for ChildrenOutletContexts!
After a few searches, I found it’s because I need to call RouterModule.forRoot()
in somewhere in my application for router-outlet to work. And for a standalone components initialization, there is a new way to do so using the new function importProvidersFrom
. Here is what the code will look like:
import { enableProdMode, importProvidersFrom } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';const routes: Routes = [];if (environment.production) {
enableProdMode();
}bootstrapApplication(AppComponent, {
providers: [
...importProvidersFrom(RouterModule.forRoot(routes)),
],
})
.catch(err => console.error(err));
And finally, it works, now we have an Angular application with no NgModule
.
Then, let’s have a lazy-loaded component
Before now you could only lazy load a module. To lazy load one component, you had to create a module for just this component then lazy load this module, but now it’s much easier. Let’s take a look at how to make it.
First, let’s create a new standalone component using the new flag --standalone
.
ng g c foo --standalone
And to use it, there is a new option in Routes called loadComponent, which works very similar to how it was working with Modules.
const routes: Routes = [
{
path: 'foo',
loadComponent: () => import('./app/foo/foo.component').then(c => c.FooComponent),
},
];
While in the production application you should export the routers from another file and import them to main.ts, but I wanted to make things very simple.
Now if you navigate to http://localhost:4200/foo you will see Foo works! And if you take a look at the network tab in the devtool, you will see it.
To make sure it’s lazy-loaded indeed, you need to start the application and then navigate to it, that’s what comes in our next and final part.
Finally, creating a reusable standalone components
I feel for old applications, this will be the most beneficial part. Now, having a reusable component is never easier, no shared NgModule
where you needed to load all of your components just to use one of them, or having a module per component where you had to create a lot of unnecessary files.
We will start just as before, create it using the new --standalone
flag.
ng g c header --standalone
In the HTML, we will simply just put:
<a routerLink="">home</a> | <a routerLink="foo">foo</a>
You will find links are not working, that’s because again you need to import RouterModule
to your component, then import the HeaderComponent
to the AppComponent
then voila, it’s working!
Here is what they will look like:
AppComponent
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HeaderComponent } from './header/header.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RouterModule, HeaderComponent],
})
export class AppComponent {
}
HeaderComponent
import { Component, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router';@Component({
selector: 'app-header',
standalone: true,
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
imports: [RouterModule],
})
export class HeaderComponent implements OnInit {constructor() {
}ngOnInit(): void {
}}
You can see the whole code in my GitHub robertIsaac/ng14:
More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter and LinkedIn. Join our community Discord.