Angular 17+ : Blending Angular's Old and New Architectures with Standalone Components
Amit Kumar
Staff engineer | Angular, React, Micro frontends, Nx, Web3, Smart Contracts
One of the most significant changes in Angular 17 is the introduction of standalone components. These components can function independently without being declared in a module. This shift has simplified project structure and reduced boilerplate code.
A newly created Angular 17 projects might not include the traditional app.module.ts and app-routing.module.ts files by default. This change is primarily due to Angular's shift towards standalone components, a feature that was introduced in Angular 14 and has become more prominent in subsequent versions, including Angular 17.
Angular 17: A Shift Towards Implicit Modules
One of the notable changes in Angular 17 is the removal of the explicit app.module.ts file. This shift towards implicit modules offers several advantages:
1. Simplified Project Structure
2. Improved Developer Experience
3. Enhanced Flexibility
4. Improved Performance
5. Reduced Complexity
The removal of the explicit app.module.ts file in Angular 17 is a significant change that offers several benefits, including a simplified project structure, improved developer experience, enhanced flexibility, and potentially improved performance. By embracing this change, developers can create more efficient and maintainable Angular applications.
While Angular 17 doesn't explicitly include an app.module.ts file by default, it still uses the concept of modules behind the scenes.
Here's why:
Accessing the Root Module:
If we ever need to access or modify the root module, we can do so using the APP_INITIALIZER token or by injecting the ApplicationRef service.
In summary: While the Angular CLI doesn't generate an explicit app.module.ts file by default, it still uses the concept of modules to structure and bootstrap our application. The root module is implicitly created and serves as the entry point for our application.
Understanding the Shift to Standalone Components
1. Standalone Components:
Simplified Structure: Reduces boilerplate by eliminating the need for module files.
Improved Tree Shaking: Potentially better performance due to more efficient bundling.
Easier Lazy Loading: Simplifies the setup for lazy-loaded modules.
2. Default Project Setup:
Angular CLI Defaults: Starting with Angular 14 and continuing into Angular 17, the Angular CLI provides options to create projects using standalone components. When you generate a new project with these defaults, it opts for standalone components, thereby omitting app.module.ts and app-routing.module.ts.
Modern Approach: This aligns with Angular's direction towards a more modular and flexible architecture.
Creating a Project with NgModules in Angular 17
If we prefer using NgModules and want the traditional app.module.ts and app-routing.module.ts files in your project, we can explicitly specify this when creating our project:
Using Angular CLI Flags:
ng new your-project-name --standalone=false
Explanation: The --standalone=false flag tells the Angular CLI to generate the project using the traditional NgModule-based architecture.
Example:
ng new my-angular-app --standalone=false
This command will generate a new Angular project named my-angular-app with app.module.ts and app-routing.module.ts included.
Migrating to Standalone Components
If you have an existing project using NgModules and want to migrate to standalone components, Angular provides a migration path. However, this process can be involved and requires careful planning. Here's a high-level overview:
For detailed steps, refer to the Angular Standalone Components Guide.
Using Routing with Standalone Components
Even without app-routing.module.ts, you can still set up routing in a standalone-based Angular project:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, RouterModule } from '@angular/router';
import { AppComponent } from './app/app.component';
import { HomeComponent } from './app/home/home.component';
import { AboutComponent } from './app/about/about.component';
const routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
];
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
Angular 17 continues to support both traditional NgModule-based architecture and the newer standalone components approach. The Angular CLI's default behavior has shifted towards standalone components to leverage their benefits, which is why app.module.ts and app-routing.module.ts might not appear in newly created projects unless explicitly requested.
If we prefer the traditional structure, use the --standalone=false flag when generating a new project. Otherwise, embrace the standalone components approach for a more modern and streamlined Angular development experience.
we can absolutely use standalone components within a traditional Angular project (with NgModules). Angular is flexible enough to allow both approaches to coexist, so we can progressively introduce standalone components into our existing NgModule-based project. Here's how we can achieve that:
Steps to Add Standalone Components in a Traditional Angular Project
领英推荐
ng generate component standalone-component-name --standalone
This will generate a component with the following structure:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-standalone-component',
standalone: true,
imports: [CommonModule],
templateUrl: './standalone-component.component.html',
styleUrls: ['./standalone-component.component.scss']
})
export class StandaloneComponent { }
Simply import the standalone component where you need it.
In your app.module.ts:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { StandaloneComponent } from './standalone-component/standalone-component.component';
@NgModule({
declarations: [
AppComponent,
// You do not need to declare the standalone component here
],
imports: [
BrowserModule,
StandaloneComponent, // Import standalone component directly
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
This works because standalone components are self-contained and do not need to be declared in an NgModule.
In app-routing.module.ts:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { StandaloneComponent } from './standalone-component/standalone-component.component';
const routes: Routes = [
{ path: 'standalone', component: StandaloneComponent },
// other routes
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
const routes: Routes = [
{
path: 'standalone',
loadComponent: () => import('./standalone-component/standalone-component.component').then(m => m.StandaloneComponent)
},
];
This lazy loads the standalone component when the user navigates to the standalone route.
Why Use Standalone Components in a Traditional NgModule Project?
We can easily create and use standalone components within a traditional Angular project (with NgModules). This approach lets us progressively adopt the standalone component model without refactoring the entire project. It also offers more flexibility for future migrations as Angular continues evolving toward a more modular structure.
Here’s an overview of the default features and project structure mechanism in Angular 17, which highlights how Angular's development has evolved:
1. Standalone Components (Optional)
Reduces the need for @NgModule, streamlining component management.
Allows components to manage their own dependencies directly via imports.
Facilitates better tree-shaking and lazy loading, enhancing performance.
2. Project Structure with Standalone Components
When creating a project without the --standalone=false flag (i.e., the default structure in Angular 17), here’s what you get:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent);
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
3. Optional Traditional Project Structure
If you create a project with --standalone=false, Angular will generate the traditional structure:
The root NgModule acts as the main module for bootstrapping the app.
This module imports all dependencies and declarations (components, directives, pipes) as in previous Angular versions.
If routing is required, this file manages the routing configuration using RouterModule.forRoot().
This traditional structure is still fully supported and can work alongside the newer standalone component approach, enabling a gradual migration path.
4. Other Key Features in Angular 17
5. Project Structure Overview in Angular 17
Whether using standalone components or the traditional module-based architecture, Angular 17 keeps the general structure clean and modular. Here’s an outline:
app.component.ts: The root component, whether standalone or declared in a module.
Standalone or NgModule Components: Each feature or component may either be a standalone component or follow the traditional NgModule structure.
Angular 17 offers flexibility by allowing you to choose between:
We can use standalone components in both new and existing Angular projects, allowing for a smooth transition and enabling modern Angular features while maintaining familiarity with the older project structure.
#Angular17 #StandaloneComponents #NgModules #WebDevelopment #FrontendArchitecture #AngularTips #ModuleFederation #IvyRendering #MicroFrontends #AngularBestPractices