Optimizing an Angular App like PRO

Santosh Yadav

GDE for Angular, GitHub Star, Auth0 Amabassador

Co-founder This is Learning

Use TrackBy with ngFor

OnPush Change Detection

Signals

@Component({
  selector: 'app-product-signal',
  templateUrl: './product-signal.component.html',
  styleUrls: ['./product-signal.component.scss'],
  standalone: true,
  imports: [JsonPipe, CommonModule],
})
export class ProductSignalComponent implements OnInit {
  product = signal<Product[]>([]);
  
  addProduct() {
    this.product.set( 
     [{
        id: crypto.randomUUID(),
        name: 'product 1',
        price: 100,
        description: 'test',
      }]
    );
  }
}

How Lazy Loading Helps

Seperate feature bundles

Load bundles on demand

Serve less code on first serve

Configure lazy-loading with Module

const routes: Routes = [
  {
    path: 'rooms',
    loadChildren: () =>
      import('./rooms/rooms.module').then((m) => m.RoomsModule),
    canActivate: [LoginGuard],
    canLoad: [LoginGuard], 
  }
];

Configure lazy-loading with Standalone Component

const routes: Routes = [
  {
    path: 'rooms',
    loadComponent: () => import('./comment/comment.component').then((m) => m.CommentComponent),
    canActivate: [LoginGuard],
    canLoad: [LoginGuard], 
  }
];

Avoid Shared Module

Text

import { NgModule } from '@angular/core';
import { OverlayModule } from '@angular/cdk/overlay';
import { CdkTreeModule } from '@angular/cdk/tree';
import { PortalModule } from '@angular/cdk/portal';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatRippleModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTreeModule } from '@angular/material/tree';

const materialModules = [
  CdkTreeModule,
  MatAutocompleteModule,
  MatButtonModule,
  MatCardModule,
  MatCheckboxModule,
  MatChipsModule,
  MatDividerModule,
  MatExpansionModule,
  MatIconModule,
  MatInputModule,
  MatListModule,
  MatMenuModule,
  MatProgressSpinnerModule,
  MatPaginatorModule,
  MatRippleModule,
  MatSelectModule,
  MatSidenavModule,
  MatSnackBarModule,
  MatSortModule,
  MatTableModule,
  MatTabsModule,
  MatToolbarModule,
  MatFormFieldModule,
  MatButtonToggleModule,
  MatTreeModule,
  OverlayModule,
  PortalModule
];

@NgModule({
  imports: [
    ...materialModules
  ],
  exports: [
    ...materialModules
  ],
})
export class MaterialModule {
}

Secondary Entrypoints

Why

With Angular Libraries you get single entrypoints.

Even a single module or component import will add huge chunk to bundle

How

Secondary Entrypoints adds only what is imported

To configure secondary entrypoints add ng-package.json to folder

nx g library-secondary-entrypoint <sec-name>

Use Standalone Component



@Component({
  selector: 'app-product-signal',
  templateUrl: './product-signal.component.html',
  styleUrls: ['./product-signal.component.scss'],
  standalone: true,
  imports: [JsonPipe, CommonModule],
})
export class ProductSignalComponent 
implements OnInit {
}
ng generate @angular/core:standalone

Migration to Standalone

Always Check What's in your bundle

How

Use source-map-explorer

ng add @ngx-builders/analyze

ng run project-name:analyze

Always use budgets

"budgets": [
  {
    "type": "initial",
    "maximumWarning": "100kb",
    "maximumError": "150kb"
  },
  {
    "type": "anyComponentStyle",
    "maximumWarning": "6kb",
    "maximumError": "10kb"
  }
]

Types of budgets

bundle - The size of a specific bundle.

initial - The initial size of the app.

allScript - The size of all scripts.

all - The size of the entire app.

Types of budgets

anyComponentStyle - This size of any one component stylesheet.

anyScript - The size of any one script.

any - The size of any file.

State Management

  • Reduce the number of API calls
  • Reduce the change detection by writing reactive code.
  • Improved performance 
  • More modular application

Available Solutions

  • NgRx

 

  • NgXs

 

  • Rx-Angular

 

  • Akita

 

Always use es modules for lodash or moments

Avoid using commonjs modules.

Angular 10+ will give warnings if commonjs module is used

use allowedCommonJsDependencies to disable warning

Tree shakable

Image Optimization

@Component({
  standalone: true
  imports: [NgOptimizedImage],
})
class MyStandaloneComponent {}

providers: [
  provideImgixLoader("https://abc.com/"),
],

<img ngSrc="logo.png" width="200" height="100">

Use Strict Mode

  • Enables strictmode in TypeScript
  • Turns on strict Angular compiler flags
  • Reduces the bundle size budgets for the initial and anyComponentStyle budget types by 75% compared to the previous defaults.
ng generate application [project-name] --strict

use CDN to serve the assets

Low network latency

High availability and better analytics

Decrease server load

Virtual Scroll

  • Render elements that can fit on the screen.

 

  • Recommended for UI with a huge list.

 

  • Improves the performance of the application

 

  • Better UX for end users

 

 

  • https://www.rx-angular.io/docs/template/api/virtual-scrolling

 

  • https://material.angular.io/cdk/scrolling/overview

Available Solutions

References

https://indepth.dev/are-you-using-scss-properly/

https://indepth.dev/stop-using-shared-material-module/

 

twitter.com/SantoshYadavDev

github.com/SantoshYadavDev

https://www.linkedin.com/in/SantoshYadavDev/

santoshyadav.dev

 

Thank you

Optimizing an Angular App

By Santosh Yadav

Optimizing an Angular App

  • 563