Angular: Advanced Tips and Best Practices

Sagarnath S
5 min readApr 7, 2023

Lazy loading : Please lazy load your modules and don’t eager load them all at once. It makes a tremendous difference in loading time even for small applications. Consider eager loading for core modules and feature modules that required to start the app and do some initial interception

A module can be loaded lazily by using the loadChildren property in the routing configuration. When a user navigates to a route with a lazy-loaded module, Angular will only load the module when it is requested. This can significantly reduce the initial load time of the application.

Let’s take an example of an e-commerce website with different modules like product catalog, shopping cart, checkout, and user profile. Each module has its own set of components, services, and routing configuration.

To implement lazy loading in this scenario, we can create separate modules for each feature and load them lazily using the loadChildren property in the routing configuration.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'products' },
{
path: 'products',
loadChildren: () => import('./product/product.module').then(m => m.ProductModule)
},
{
path: 'cart',
loadChildren: () => import('./cart/cart.module').then(m => m.CartModule)
},
{
path: 'checkout',
loadChildren: () => import('./checkout/checkout.module').then(m => m.CheckoutModule)
},
{
path: 'profile',
loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule)
},
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

In the above code, we have defined the routing configuration for our application. Each module is loaded lazily using the loadChildren property, which takes a function that returns a promise of the module to be loaded.

Let’s take a closer look at one of the lazy-loaded modules, product.module.ts:


import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';

const routes: Routes = [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductDetailComponent },
];

@NgModule({
declarations: [ProductListComponent, ProductDetailComponent],
imports: [
CommonModule,
RouterModule.forChild(routes)
]
})
export class ProductModule { }

In the above code, we have defined the ProductModule module, which includes the ProductListComponent and ProductDetailComponent components. The module also has its own routing configuration, which is loaded lazily when a user navigates to the /products route.

Service in subModule Providers : Submodule is a feature module that is nested within another module. Service providers are used to provide services to components and other parts of an Angular application.

To provide a service at the submodule level, you can use the providers property of the @NgModule decorator for the submodule.

For example:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SubModuleComponent } from './sub-module.component';
import { SubModuleService } from './sub-module.service';

@NgModule({
declarations: [SubModuleComponent],
imports: [CommonModule],
providers: [SubModuleService]
})
export class SubModule { }

This defines the SubModuleService as a provider for the SubModule feature module. This means that the service will only be available to components that are declared within this module or its child modules.

Services are singleton in the module provided. Adding them to other module providers will create a new instance.

If you want to provide the service at the root level of the application, you can use the providedIn property of the @Injectable decorator for the service.

For example:

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class SubModuleService {
// service code here
}
  • root: One instance of the service for the whole application
  • any: One instance for the eagerly loaded modules, different instance for every lazy module
  • platform: One instance for all angular applications running on the same page (useful for shared service modules)

trackBy: You rendered a collection with *ngFor, and now you have to get new data from an HTTP request and reassign them to the same collection. This will cause Angular to remove all DOM elements associated with the data and re-render all DOM elements again. Having a large collection could cause a performance problem since DOM manipulations are expensive.

trackBy comes to the rescue. By using trackBy, rendering will happen only for the collection elements that have been modified(new/removed).

@Component({
selector: 'my-component',
template: `
<ul>
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
</ul>
`,
})
export class MyComponent {
items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];

trackById(index: number, item: any): number {
return item.id;
}
}

In this example, we have a list of items with unique IDs. The *ngFor directive iterates over the list and displays the name of each item. We also pass a trackBy function trackById to the *ngFor directive. This function returns the ID of each item, which serves as a unique identifier.

By providing a unique identifier, Angular can track which items have been added, removed, or modified. This allows Angular to update the DOM efficiently and minimize unnecessary re-renders.

Change detection strategy: It is refers to the process of detecting changes in the component’s properties and updating the view accordingly. Angular provides two types of change detection strategies:

  1. Default Change Detection Strategy
  2. OnPush Change Detection Strategy

By default, Angular runs change detection on all components for every event that occurred. It’s the default ChangeDetectionStrategy.Default. Having many components and a large number of elements could result in performance issues. To deal with that we can set ChangeDetectionStrategy.OnPush on the components we want.

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
selector: 'app-counter',
template: `
<h2>{{data}}</h2>
<button (click)="setNewData()">Get Data</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
@Input() data;
constructor(private cd : ChangeDetectorRef){}

setNewData(){
getData().then(d=>{
this.data = d;
this.cd.detectChanges();
})
}
}

Pure and impure Pipes: Pipes are used to transform data before it is displayed in the view. Pipes can be either pure or impure, and the difference between them is how they handle changes in the data.

A pure pipe is a pipe that only depends on its input and does not have any side effects. This means that if the input data remains the same, the output of the pipe will also remain the same.

An impure pipe, on the other hand, can have side effects and depends on external factors such as global state or user input. Impure pipes can be used when the output needs to be updated frequently or when the pipe depends on external factors.

@Pipe({ 
name: 'softDrink'
})
export class softDrinkPipe implements PipeTransform {
transform(drinks: Drinks[]) {
return drinks.filter(drink => drink.isSoftDrink);
}
}

Now if we add a new drink somewhere in our component code.


addNewDrink(){
this.cars.push({name:'CocaCola', isSoftDrink:true});
}

Nothing happens. The view is not updating. This is because the drinks array reference is the same. If we want to see the changes, we have to make our pipe impure or re-reference the drinks array.

@Pipe({ 
name: 'softDrink',
pure: false
})

Follow me on Medium or LinkedIn to read more about Angular and TS!

--

--

Sagarnath S

Software Development Engineer - I @CareStack || Web Developer || Angular || ASP.Net Core || C# || TypeScript || HTML || CSS