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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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  { }

Enter fullscreen mode Exit fullscreen mode
  • No entry gets made to the app.module by the CLI.
  • Through the imports property section, other modules—for instance, HttpClientModule and FormsModule—can be brought in. The CommonModule gets imported as a default.
  • To designate the component as standalone, the standalone property must be set to true.

To give the app its structure, insert this html.

<div class="container">
    <ng-content></ng-content>
</div>
<footer>2022</footer>
Enter fullscreen mode Exit fullscreen mode

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 { }

Enter fullscreen mode Exit fullscreen mode

Head over to app.component.html and drop the component in.

<app-container-layout>
</app-container-layout>
Enter fullscreen mode Exit fullscreen mode

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)   
Enter fullscreen mode Exit fullscreen mode

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'
}

Enter fullscreen mode Exit fullscreen mode
<img [src]="logoName">
Enter fullscreen mode Exit fullscreen mode

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  {
}

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)   
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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 {

}
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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  { }
Enter fullscreen mode Exit fullscreen mode
<app-logo></app-logo>
<app-navigation></app-navigation>
<div class="container">
    <ng-content></ng-content>
</div>
<footer>2022</footer>
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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
  }
]
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

Within app.component.html, insert the router outlet so the components become visible.

<app-container-layout>
  <router-outlet></router-outlet>
</app-container-layout>

Enter fullscreen mode Exit fullscreen mode

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)
  }
]
Enter fullscreen mode Exit fullscreen mode

After persisting the modifications, reload the application, then move through every route to observe the individual chunked responses delivered per component.

Final Result

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';
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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));

Enter fullscreen mode Exit fullscreen mode

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.

Source Code.

Here’s where to explore Standalone Components further: