With the latest advancement in the Angular framework ecosystem, we are heading towards a more robust architecture of standalone components which helps to maintain the maximum level of decoupling, reusability, maintainability and ofcource performance.
[lwptoc]
Standalone Components in Angular 17
Angular 17 introduced many new features, targeting the best optimal performance and developer experience. A few of those include Standalone Components, Deferred Views, Improved Server-Side Rendering (SSR), TypeScript 5.2 Support, Custom Element Binding and much more!
But today we will stroll around a much-anticipated topic which is Standalone Components and will clear the air on various queries related to those with detailed practical solutions and examples to try out.
Standalone Components can be defined as independent of the traditional NgModule structure.
We can easily convert a tradition-dependent component into a standalone by adding and setting the standalone: true
@Component({
selector: 'app-my-component',
template: `<h1>Hello from Standalone World!</h1>`,
standalone: true, // Mark as standalone
})
export class MyComponent {
// ... Your component logic here
}
Benefits of Standalone Components
Let’s discuss how choosing Standalone Components can make life more easy:
1. Simple Code
By using Standalone Components, we can easily get rid of complexity and loads of boilerplate when using NgModule. It can have all the required dependencies packed in its class and decorator so it becomes easy to maintain and read it.
2. Improved Performance
It results in smaller bundle sizes which results in faster loads and startup time to enhance the user experience. It prevents unnecessary code by pointing to required dependencies only.
3. Advanced Feature Support
Standalone Components are no less, with support for Dependency Injection, we can inject any type of service that is possible with NgModules.
Dependency Injection
Let’s have a look at the following standalone components with most of the possible injections including Pipe, Service, Providers etc that can be added to the @Component decorator’s metadata:
import { Component, Inject } from '@angular/core';
import { ProductService } from './product.service';
import { CurrencyPipe } from './currency.pipe';
@Component({
selector: 'app-product-list',
template: `
<h1>Product List</h1>
<ul>
<li *ngFor="let product of products">
{{ product.name }} - {{ product.price | currency }}
<span *ngIf="product.isSale">(Sale!)</span>
</li>
</ul>
`,
standalone: true, // Mark as standalone
imports: [ProductService, CurrencyPipe], // Import service and pipe
providers: [
{ provide: 'discount', useValue: 0.1 } // Provider for discount value
]
})
export class ProductListComponent {
products: Product[] = [];
discount: number;
constructor(private productService: ProductService, @Inject('discount') private discountValue: number) {
this.products = this.productService.getProducts();
this.discount = discountValue; // Inject discount value
}
// ... existing logic
}
The above example clearly shows how we can inject services, add providers’ properties, using pipes in standalone components.
Lazy Loading
Lazy Loading is a very important and useful feature that we usually see packed while using modules. But with Standalone Components, we can achieve similar behaviour to load components lazily using the loadComponent
function during the setup of Routes.
Let’s have a look at the implementation of lazy loading stand-alone components:
In the following Routing Module, we are configured the Routes with Product Standalone Components which are getting loaded lazily by leveraging the loadComponent
function:
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component'; // Assume this exists
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', loadComponent: () => import('./product-list/product-list.component').then(m => m.ProductListComponent) },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
In the above module, we have configured the route for /products
using loadComponent
which returns a promise and is resolved by loading the ProductListComponent
.
To further enhance the loading fluctuations, we can add the preload: true
to it so that the components is loaded upfront by being enabled lazy as shown below:
const routes: Routes = [
// Other routes...
{ path: 'lazy-component', loadComponent: () => import('./lazy.component').then(m => m.LazyComponent), preload: true },
];
How to Import Components and Directives in Standalone Components?
Similar to NgModules, Standalone Components provide flexibility to import components and directives. Let’s have a look at various ways to attain this:
1. Direct Import
We can easily import the component or directive from the source file and update the imports
array:
import { MyComponent } from './my-component';
@Component({
// ... other component properties
imports: [MyComponent]
})
export class MyStandaloneComponent {}
2. Using @anglular/core
By using the @angular/core module to import the core components/ directives for example ngFor, ngIf etc which comes under CommonModule:
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
@Component({
// ... other component properties
imports: [CommonModule, RouterOutlet]
})
export class MyStandaloneComponent {}
3. Using Third-Party Libraries:
If any third-party modules are required, we can directly pull them into the imports array instead of depending on MgModules:
import { MatSliderModule } from '@angular/material/slider';
@Component({
// ... other component properties
imports: [MatSliderModule]
})
export class MyStandaloneComponent {}
4. Using Lazy Loading
We can also leverage the lazy loading mechanism to import using the loadComponent
function within the imports
array:
@Component({
// ... other component properties
imports: [
() => import('./lazy-loaded-component').then(m => m.LazyLoadedComponent)
]
})
export class MyStandaloneComponent {}
5. Using Custom Provider
We can define a custom provider array in the imports section to provide any services or values:
@Component({
// ... other component properties
imports: [
{ provide: 'myService', useValue: myServiceInstance }
]
})
export class MyStandaloneComponent {}
Turn Off Standalone Architecture in Angular 17
Although we are thrilled to learn the new possibilities we can achieve, but transitioning and grooming take time. By moving to the latest NG CLI, it allows to creation of applications without AppModule or NgModule.
Here is how you can create the traditional Angular 17 application with AppModule and a similar structure we had before. Add the --no-standalone
flag while creating the application and get the old good flavours as shown below:
ng new my-app --no-standalone
Conclusion
In this detailed discussion on Angular Standalone Components, we touched on various features, use cases, and benefits of using them in applications. With robust support of Lazy Loading, Modularity, and Optimisation enablement, Standalone Components open doors to create web component-like structures by leveraging custom element components. Hope this guide will help scratch the surface towards more endless possibilities.
Stay Tuned, there’s a lot to come. Thank you…
Leave a Reply