Starting with version 14, Standalone Components give us a straightforward path to building apps without modules. They lower the learning curve for newcomers, letting them get productive with Angular without first grasping how ngModule works.
Note: The developer preview tag still applies to Standalone Components, so hold off on using them in production environments for now.
The Scenario
Our task for today: put together a conventional module-based Angular app with CLI/14 to cover these topics.
- How to set up standalone components.
- Integrating them into the
app.module. - Nesting a child's standalone within a parent standalone.
- Bringing Modules into a standalone component.
- Handling Routing and lazy loading via Standalone Components.
- Shifting toward a module-less structure.
Setup The Project
Kicking things off, we set up a project with Angular 14. Instead of a global installation, we leverage npx to spin up a fresh project with the most recent angular/cli release.
npx @angular/cli new landing-page
The output of the generator is a conventional Angular project, containing both app.module and app.component.ts.
Here’s what the directory layout looks like:
src
app/
components/
pages/
app.component.css
app.component.ts
app.component.html
app.module.ts
app.routes.ts
assets
enviroments
favicon.ico
index.html
main.ts
polyfills.ts
styles.css
and more files.
Create Your First Standalone Components
When you need Angular/cli to create a standalone component, the --standalone flag is what triggers it.
Take the container layout as an example—here’s the command you’d execute to generate it.
ng g c components/container-layout --standalone
CREATE src/app/components/container-layout/container-layout.component.html (31 bytes)
CREATE src/app/components/container-layout/container-layout.component.spec.ts (658 bytes)
CREATE src/app/components/container-layout/container-layout.component.ts (409 bytes)
CREATE src/app/components/container-layout/container-layout.component.css (0 bytes)
Two fresh properties are introduced on the component, yet certain caveats apply.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-container-layout',
standalone: true,
imports: [CommonModule],
templateUrl: './container-layout.component.html',
styleUrls: ['./container-layout.component.css']
})
export class ContainerLayoutComponent { }
- No entry gets made to the
app.moduleby the CLI. - Through the imports property section, other modules—for instance,
HttpClientModuleandFormsModule—can be brought in. TheCommonModulegets imported as a default. - To designate the component as standalone, the
standaloneproperty must be set totrue.
To give the app its structure, insert this html.
<div class="container">
<ng-content></ng-content>
</div>
<footer>2022</footer>
Register and Use Standalone Components
The container-layout component stays out of app.module. To make it available inside app.component, open the module file and add it to the imports array.
That is exactly how we handle modules.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ContainerLayoutComponent } from './components/container-layout/container-layout.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ContainerLayoutComponent
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Head over to app.component.html and drop the component in.
<app-container-layout>
</app-container-layout>
With container-layout now in place, the application is ready for a standalone child component.
Use Standalone Child Components
Following the same approach used for container-layout, create another standalone component called logo via the CLI by passing the --standalone flag.
ng g c logo --standalone
CREATE src/app/logo/logo.component.html (19 bytes)
CREATE src/app/logo/logo.component.spec.ts (580 bytes)
CREATE src/app/logo/logo.component.ts (362 bytes)
CREATE src/app/logo/logo.component.css (0 bytes)
Within the container layout component, the logo component is responsible for rendering the image.
import { Component, Input, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-logo',
standalone: true,
imports: [CommonModule],
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.css']
})
export class LogoComponent {
@Input() logoName = 'https://avatars.dicebear.com/api/adventurer-neutral/mail%40ashallendesign.co.uk.svg'
}
<img [src]="logoName">
Within the imports array of the layout, include the logo component.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LogoComponent } from '../logo/logo.component';
@Component({
selector: 'app-container-layout',
standalone: true,
imports: [CommonModule, LogoComponent],
templateUrl: './container-layout.component.html',
styleUrls: ['./container-layout.component.css']
})
export class ContainerLayoutComponent {
}
Inside the layout component's template, place the <app-logo> tag.
<app-logo></app-logo>
<div class="container">
<ng-content></ng-content>
</div>
<footer>2022</footer>
Once you save the changes, the app.component will display the layout, including the logo, within the app.
Use External Modules with Standalone Components
Now we examine how external modules, such as ReactiveForms, can be integrated into a standalone component. Start by generating a fresh standalone component registration through the CLI.
ng g c register --standalone
CREATE src/app/register/register.component.html (23 bytes)
CREATE src/app/register/register.component.spec.ts (608 bytes)
CREATE src/app/register/register.component.ts (378 bytes)
CREATE src/app/register/register.component.css (0 bytes)
To build the form, bring the ReactiveFormsModule into the imports array. Then, with FormGroup and FormControl, define the fields name and email.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-register',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
registerForm = new FormGroup({
name: new FormControl(''),
email: new FormControl(''),
});
sendForm() {
console.log(this.registerForm.value);
}
To wrap up, attach the sendForm function to the page so the form inputs are visible. Insert the template code inside the form, wiring the ngSubmit event to that same sendForm handler.
<form [formGroup]="registerForm" (ngSubmit)="sendForm()">
<label for="first-name">Name: </label>
<input id="first-name" type="text" formControlName="name">
<label for="email">Email</label>
<input id="email" type="email" formControlName="email">
<button type="submit">Send</button>
</form>
For further details, see the Reactive forms documentation.
Now that the ReactiveForms Module has been integrated into the standalone components, the next topic is how routing works with them.
Navigation and Routing for Standalone Components
Handling navigation and routing pulls in dependencies such as RouterModule; the navigation part comes first.
Start by generating a standalone navigation component through Angular CLI.
ng g c navigation --standalone
CREATE src/app/components/navigation/navigation.component.html (25 bytes)
CREATE src/app/components/navigation/navigation.component.spec.ts (622 bytes)
CREATE src/app/components/navigation/navigation.component.ts (386 bytes)
CREATE src/app/components/navigation/navigation.component.css (0 bytes)
The navigation component must have the RouterModule imported for it to work with the routerLink and routerLinkActive directives.
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-navigation',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent {
}
Swap the existing HTML for the structure below, which builds the navigation menu via router directives.
<ul class="navigation">
<li>
<a routerLink="/home" routerLinkActive="active">Home</a>
</li>
<li>
<a routerLink="/domains" routerLinkActive="active">Others Domains</a>
</li>
<li>
<a routerLink="/about" routerLinkActive="active">About</a>
</li>
</ul>
Integrating container-layout into the component requires the navigation to be pulled in via its import statement, then referenced within the HTML markup.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {LogoComponent} from "../logo/logo.component";
import {NavigationComponent} from "../navigation/navigation.component";
@Component({
selector: 'app-container-layout',
standalone: true,
imports: [CommonModule, LogoComponent, NavigationComponent],
templateUrl: './container-layout.component.html',
styleUrls: ['./container-layout.component.css']
})
export class ContainerLayoutComponent { }
<app-logo></app-logo>
<app-navigation></app-navigation>
<div class="container">
<ng-content></ng-content>
</div>
<footer>2022</footer>
With the navigation in place, the next step is to set up the pages and their corresponding routes.
Now, generate the home and domains components via the Angular CLI; these will serve as the routing pages.
ng g c domains --standalone
CREATE src/app/pages/domains/domains.component.html (22 bytes)
CREATE src/app/pages/domains/domains.component.spec.ts (601 bytes)
CREATE src/app/pages/domains/domains.component.ts (374 bytes)
CREATE src/app/pages/domains/domains.component.css (0 bytes)
ng g c about --standalone
CREATE src/app/pages/about/about.component.html (20 bytes)
CREATE src/app/pages/about/about.component.spec.ts (587 bytes)
CREATE src/app/pages/about/about.component.ts (366 bytes)
CREATE src/app/pages/about/about.component.css (0 bytes)
Attach the snippet below to every component rendered on the current view:
domains:
<h2>Other domains</h2>
<ul>
<li>www.aprendetesting.com</li>
<li>www.aprende-singlespa.com</li>
</ul>
Regarding:
<h1>About</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Facilis perspiciatis, sunt laudantium architecto repellendus illo aspernatur minus tenetur consequuntur facere vitae natus molestiae, in, ad et. Laborum sed amet adipisci et recusandae, illo perferendis quae deleniti id modi laboriosam ullam nulla expedita sit labore. Odit, vel exercitationem iure vitae dolores sequi labore quasi nemo, non optio rem totam obcaecati aspernatur culpa nihil perferendis itaque corporis in maxime dolorem quidem magni? Facere cupiditate fuga sunt quam praesentium. Dicta id explicabo obcaecati.
</p>
Inside the home component, we'll embed the register as a standalone element, just as before, by adding it to the imports array.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RegisterComponent } from '../../components/register/register.component';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, RegisterComponent ],
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
domainName = "www.aprendetypescript.com";
price = 100;
}
Incorporate the template by placing the app-register element within it.
<div class="sections">
<div class="welcome">
<h1>Yes, {{domainName}} available for sale</h1>
<p>For instant purchase. please make a PayPal request</p>
<button>Buy now for {{price | currency}}</button>
</div>
<app-register></app-register>
</div>
Configure Routing
Setting up routing here follows the exact same pattern you're used to. You create an array where each route object pairs a path with the component it should render.
For a deeper dive, see Angular's official routing guide
import { Routes } from '@angular/router';
import { AboutComponent } from './pages/about/about.component';
import { DomainsComponent } from './pages/domains/domains.component';
import { HomeComponent } from './pages/home/home.component';
export const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
component: HomeComponent,
},
{
path: 'domains',
component: DomainsComponent
},
{
path: 'about',
component: AboutComponent
}
]
Within app.module, bring in the RouterModule, supplying the routes as its argument.
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ContainerLayoutComponent,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Within app.component.html, insert the router outlet so the components become visible.
<app-container-layout>
<router-outlet></router-outlet>
</app-container-layout>
Once the app is open, we can roam through its pages via the navigation bar.
Lazy-Loading Standalone Components.
The loadComponent function introduces a subtle twist compared to what we do with lazy-loaded modules.
Swap the component out for the loadComponent() routine, handing it the function responsible for fetching the component.
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent)
},
{
path: 'domains',
loadComponent: () => import('./pages/domains/domains.component').then(m => m.DomainsComponent)
},
{
path: 'about',
loadComponent: () => import('./pages/about/about.component').then(m => m.AboutComponent)
}
]
After persisting the modifications, reload the application, then move through every route to observe the individual chunked responses delivered per component.
Convert Module-Less Application
Making the switch to a module-less setup is the final task. Certain adjustments are required for this conversion.
Start by turning app.component.ts into a standalone component. Set the standalone property to true and bring in any components or modules that are needed.
import { Component } from '@angular/core';
import {CommonModule} from "@angular/common";
import {RouterModule} from "@angular/router";
import {ContainerLayoutComponent} from "./components/container-layout/container-layout.component";
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterModule, ContainerLayoutComponent],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'landing-page';
}
Inside main.ts, you’ll work with bootstrapApplication and importProvidersFrom as your key utilities.
Begin by stripping out platformBrowserDynamic so that AppModule no longer comes into play.
platformBrowserDynamic().bootstrapModule(AppModule)
The bootstrapApplication function is what you call, taking two arguments: the root component—here our AppComponent—plus a configuration object that can hold, among other things, providers.
For the providers, importProvidersFrom comes into play, letting us pull in something like the RouterModule. But before that, the routes themselves have to be defined upfront.
What you end up with is this:
import {enableProdMode, importProvidersFrom} from '@angular/core';
import { environment } from './environments/environment';
import {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
import {RouterModule} from "@angular/router";
import {routes} from "./app/routes";
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent, {
providers:[
importProvidersFrom(RouterModule.forRoot(routes))
]
})
.catch(err => console.error(err));
As a final step, delete the app.module.ts, and transition your application to a module-less setup that supports lazy loading, routing, and integrating third-party modules such as dynamic forms.
Recap
You now know how to build standalone components, import modules, use standalone components in routing and lazy loading, and more. This knowledge should assist you in crafting an application that runs without any modules.
Here’s where to explore Standalone Components further:

