Angular 17 Standalone Components – Lazy Loading, Dependency Injection, Hooks and Much More!

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.

 

 

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 Comment

Your email address will not be published. Required fields are marked *