Angular recently released Standalone Components in an effort to address one of their highest voted community issues; to make NgModule
optional.
This was of course met with great excitement from the community, and it offers a much simpler approach to the development of Angular applications.
Prefer a video version? Weβve got you covered! Check out our video on this here https://youtu.be/e-BpE9d3NIw
Originally with Angular, we would bootstrap an AppModule
that declared an AppComponent
and this would be seen to be the root of our application. This was achieved by calling the bootstrapModule
function in the src/main.ts
file. However, as the goal was to make NgModule
optional, Angular needed to create a method of bootstrapping a Standalone Component. Therefore, they created the bootstrapApplication
function that allows for a Standalone Component to be bootstrapped, as well as allowing top-level providers such as Routing to be initialized from the src/main.ts
file.
Despite now having an approach for building Angular applications with no NgModules
, the Angular CLI does not yet provide a schematic to generate this for us automatically. It currently still defaults to the NgModule
-based approach to application development.
With Standalone Components offering a potentially much improved developer experience, we felt it made sense to allow Standalone Component-based applications and libraries to be generated from a single command, filling the void in the Angular CLI. So we updated our @nrwl/angular
plugin to do just that!
This article will walk through how to set up an Nx Workspace with the @nrwl/angular
plugin preinstalled, and then how to use the generators the plugin offers to scaffold Standalone Component-based applications and libraries from the CLI. It will also cover how to automatically wire routing between the application and lazy-loaded feature libraries.
Note: I have renamed the concept of "Feature Modules" to "Feature Libraries" as the former term no longer makes sense in a Standalone Component world.
Initial Setup
First, create a new Nx Workspace by running the following command:
npx create-nx-workspace@latest myorg --preset=angular --appName=app1 --style=css
You'll be prompted for some additional options, you can answer them how you please.
The command will create a new directory for us named myorg
. So we'll want to ensure we are working in that directory by running:
cd myorg
The first command will have done the following:
- Created our Nx Workspace
- Installed the
@nrwl/angular
plugin and its required dependencies - Created an initial application named
app1
The application it created will be following the NgModule
approach, so we can safely ignore it.
Generating a Standalone Component-base Application
However, we really want to check out a Standalone Component-based application, so lets go ahead and generate one!
npx nx g @nrwl/angular:app myapp --standalone --routing
This command will generate our application and also configure an initial routing setup for us. You'll notice immediately that there was no app.module.ts
file created.
If we look at apps/myapp/src/app/app.component.ts
, we'll see the following
import { NxWelcomeComponent } from './nx-welcome.component';
import { RouterModule } from '@angular/router';
import { Component } from '@angular/core';
@Component({
standalone: true,
imports: [NxWelcomeComponent, RouterModule],
selector: 'myorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'myapp';
}
There are two main things to call out here, so let's take a look at them.
@Component({
standalone: true,
...
})
We can see a new boolean standalone: true
added to the decorator metadata. This tells Angular and its compiler that this Component is a Standalone Component and therefore does not be declared in an NgModule
to be used.
@Component({
imports: [NxWelcomeComponent, RouterModule],
...
})
An imports
property is also set in the decorator metadata, and it allows us to import Standalone Components and NgModules that our Component requires to be compiled and function correctly. We can now use directives, services, pipes etc. that would be exported by NgModules in our component, without our component requiring a parent NgModule
. This provides interoperability between NgModules and Standalone Components which is incredibly useful while we still have NgModules
around, either from the official Angular packages or from third-party packages.
If we run npx nx serve myapp
and navigate to localhost:4200
we can see that the application functions exactly the same as with an NgModule
-based app.
I mentioned previously that the src/main.ts
file also changes to support Standalone Components. It's worth taking a look at it to see the differences, but we generate what is required automatically for you anyway!
Generating a Standalone Component Feature Library
Angular developers will be familiar with the concept of Feature Modules, however, as we are not using NgModules
this term doesn't quite make sense. Nx has always had the concept of Workspace Libraries, where an application can be split into domain libraries that are consumed directly by the application and do not need to be published.
By combining both of these concepts, we can create the term "Feature Library" which allows us the same benefits of Feature Modules but with Standalone Components.
In Nx, we can generate a library for our feature, have it create an initial Standalone Component and allow it to be routed to, eagerly or lazily, by the application.
This will make more sense after we generate a library which can be done by running the following command
npx nx g @nrwl/angular:library mylib --standalone --routing
You'll see that this command creates a Standalone Component named mylib
as well as a routes.ts
file. If we take a closer look at routes.ts
, we'll see that it is just a standard Route[]
configuration and that it gets exported from index.ts
.
import { Route } from '@angular/router';
import { MylibComponent } from './mylib/mylib.component';
export const MYLIB_ROUTES: Route[] = [{ path: '', component: MylibComponent }];
Angular realised that by making NgModule
optional, they needed a better method to handle routing that by having to use RouterModule.forChild()
and their answer was to allow loadChildren
to point directly to the exported Route[]
configuration.
We can edit apps/myapp/src/main.ts
to add a route to our new feature library:
import { enableProdMode, importProvidersFrom } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
RouterModule.forRoot([
{
path: 'mylib',
loadChildren: () => import('@myorg/mylib').then(m => m.MYLIB_ROUTES)
}
], { initialNavigation: 'enabledBlocking' })
),
],
}).catch((err) => console.error(err));
You can see above that we just point directly to the exported const MYLIB_ROUTES
in our route. By running npx nx serve myapp
and navigating to http://localhost:4200/mylib
you can see at the bottom that the Standalone Component has been rendered correctly!
But we can do more
The @nrwl/angular:library
generator also offers support for automatically wiring the route to your Feature Library to your application, all from the one command. Let's generate a new library to see this in action.
npx nx g @nrwl/angular:library dashboard --standalone --routing --lazy --parent=apps/myapp/src/main.ts
This command will
- generate our
dashboard
library with a Standalone Component - configure routing for the library
- attach to the route to
myapp
If we take a look at apps/myapp/src/main.ts
again, we can see our new route was added automatically!
import { enableProdMode, importProvidersFrom } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{
path: 'dashboard',
loadChildren: () =>
import('@myorg/dashboard').then((m) => m.DASHBOARD_ROUTES),
},
{
path: 'mylib',
loadChildren: () =>
import('@myorg/mylib').then((m) => m.MYLIB_ROUTES),
},
],
{ initialNavigation: 'enabledBlocking' }
)
),
],
}).catch((err) => console.error(err));
Conclusion
Standalone Components offers a much better DX than NgModule
based approach to Angular application development and Nx aims to make it as straightforward and as simple as possible to adopt!
Learn More
π§ Nx Docs
π©βπ» Nx GitHub
π¬ Nrwl Community Slack
πΉ Nrwl Youtube Channel
π₯ Free Egghead course
π§ Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us π
Also, if you liked this, click the π and make sure to follow Colum and Nx on Twitter for more!
Top comments (1)
I know this tutorial is about standalone components, but I canβt see why so many Devs have asked for this feature.
NgModule is brilliant for grouping components together, that share the same libraries, like Material.
For large enterprise applications with many sections/sub sections, NgModule is invaluable.