Skip to content

Speed Up Angular App

make your angular app faster

Compression

compression reduces file sizes by a decent margin, so it will reduce the take required to load the wep app: first you need to create a script that will automatically compress all the files in the dist folder and its subfolders as well this script will use gzip and brotli algorithms: compress.ts

import {readFileSync, writeFileSync} from 'fs';
import {compress} from 'brotli';
import {gzip} from 'compressing';
import {resolve} from 'path';
const {readdir} = require('fs').promises;

const distFolder = './dist/browser';
const brotliSettings = {
extension: 'br',
skipLarger: true,
mode: 1, // 0 = generic, 1 = text, 2 = font (WOFF2)
quality: 11, // 0 - 11,
lgwin: 12, // default
threshold: 10240
};

async function* getFiles(dir: string) {
const dirents = await readdir(dir, {withFileTypes: true});
for (const dirent of dirents) {
const res = resolve(dir, dirent.name);
if (dirent.isDirectory()) {
yield* getFiles(res);
} else {
yield res;
}
}
}

function brotliCompression(input: string, output: string, settings: any) {
const result = compress(readFileSync(input), settings);
if (result) writeFileSync(output, result);
}

function gzipCompression(input: string, output: string) {
return gzip.compressFile(input, output);
}

async function AllCompression(file: string, settings: any) {
brotliCompression(file, file + '.br', settings);
await gzipCompression(file, file + '.gz');
}

async function compressFolderFiles(distFolder: string) {
for await (const file of getFiles(distFolder)) {
if (
file.endsWith('.ico') ||
file.endsWith('.jpg') ||
file.endsWith('.jpeg') ||
file.endsWith('.png')
)
brotliSettings.mode = 0;
else if (
file.endsWith('.js') ||
file.endsWith('.css') ||
file.endsWith('.html') ||
file.endsWith('.svg')
)
brotliSettings.mode = 1;
else if (
file.endsWith('.ttf') ||
file.endsWith('.eot') ||
file.endsWith('.woff') ||
file.endsWith('.woff2')
)
brotliSettings.mode = 2;
else continue;

console.log('compressing: ', file);
await AllCompression(file, brotliSettings);
}
}

compressFolderFiles(distFolder);

then add this to your package.json:

"compress": "tsc compress.ts && node compress.js",

Lazy Loaded Modules

split your web app to separate modules and load them on demand using the import syntax like following

const routes: Routes = [
{
path: 'home',
loadChildren: () =>
import('./home.module').then(
m => m.HomeModule
)
},
]

Preload Strategy

preload strategy allow you to load modules, before the user requested them so the pages will load faster, I use ngx-quicklink package to accomplish this in app.module.ts

import { QuicklinkModule } from 'ngx-quicklink';

@NgModule({
imports: [
QuicklinkModule,
]
})

in `app-routing.module.ts

import { QuicklinkStrategy } from 'ngx-quicklink';

@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: QuicklinkStrategy })],
})

SSR

ssr enables your page to be rendered on the server, which speed up the load time, and first contentfull paint, also improves seo angular does everything automatically, just run the following cmd

ng add @nguniversal/express-engine

to test it out run:

npm run dev:ssr

OnPush change detection

on push strategy improve performance by preventing angular from constantly checking for changes, instead it checks for change only on few cases, and if you requested it programatically.

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

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./home.component.scss']
})

you can fire change detection manually using ChangeDetectorRef

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

private user: User;
constructor(
private ref: ChangeDetectorRef
) {}

updateUser() {
user.name = 'trump';
this.ref.markForCheck();
}

Resources Lazy Loading

load css files only if they are needed create a server called LazyLoadResoureces

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

interface Resource<T> {
tag: string
attr: string
value: string
attr2: string
value2: string | boolean
parentTag: string
}

@Injectable({
providedIn: 'root'
})
export class LazyLoadResourcesService {

private _bootstrapJs: Resource<HTMLScriptElement> = { tag: 'script', attr: 'src', value: "bootstrapjs.min.js", attr2: "async", value2: true, parentTag: 'body' }
private _jqueryJs: Resource<HTMLScriptElement> = { tag: 'script', attr: 'src', value: "jquery.min.js", attr2: "async", value2: null, parentTag: 'body' }
private _gtagJs: Resource<HTMLScriptElement> = { tag: 'script', attr: 'src', value: "https://www.googletagmanager.com/gtag/js", attr2: "defer", value2: true, parentTag: 'body' }

private _bootstrapCss: Resource<HTMLLinkElement> = { tag: 'link', attr: 'href', value: "bootstrap.min.css", attr2: "rel", value2: "stylesheet", parentTag: 'head' }
private _stylesCss: Resource<HTMLLinkElement> = { tag: 'link', attr: 'href', value: "styles.min.css", attr2: "rel", value2: "stylesheet", parentTag: 'head' }
private _fontAwesomeCss: Resource<HTMLLinkElement> = { tag: 'link', attr: 'href', value: "font-awesome.min.css", attr2: "rel", value2: "stylesheet", parentTag: 'head' }


public loadAuthModuleResources(){
!this._ResourceExists(this._gtagJs) && this._loadResource(this._gtagJs)
!this._ResourceExists(this._bootstrapCss) && this._loadResource(this._bootstrapCss)
!this._ResourceExists(this._stylesCss) && this._loadResource(this._stylesCss)
!this._ResourceExists(this._fontAwesomeCss) && this._loadResource(this._fontAwesomeCss)
}

public loadDashboardModuleResources(){
!this._ResourceExists(this._gtagJs) && this._loadResource(this._gtagJs)
!this._ResourceExists(this._stylesCss) && this._loadResource(this._stylesCss)
!this._ResourceExists(this._bootstrapCss) && this._loadResource(this._bootstrapCss)
!this._ResourceExists(this._fontAwesomeCss) && this._loadResource(this._fontAwesomeCss)
!this._ResourceExists(this._jqueryJs) && this._loadResource(this._jqueryJs)
!this._ResourceExists(this._bootstrapJs) && this._loadResource(this._bootstrapJs)
}

private _loadResource<T>(res: Resource<T>): void{
const element = window.document.createElement(res.tag) as unknown as T
element[res.attr] = res.value
element[res.attr2] = res.value2
window.document[res.parentTag].appendChild(element)
}

private _ResourceExists<T>(res: Resource<T>): boolean{
return !!window.document.querySelector(`${res.tag}[${res.attr}="${res.value}"][${res.attr2}="${res.value2}"]`)
}
}

then use it in your components:

constructor(
private _lazyLoadResourcesService: LazyLoadResourcesService,
) { }

ngOnInit(): void {
this._lazyLoadResourcesService.loadAuthModuleResources()
}

Use Async Pipe

whenever you loaded data from external apis, just to display them on the view, you should use async pipe.

<ul>
<li *ngFor="let product of productsList$ | async">
<!-- { { product.name } } -->
</li>
</ul>

Misc

  • implement resource budgeting
  • reduct usage of third party libs
  • analyse js bundles with source-map-explorer
  • improve web vitals matrix
  • audit the app with lighthouse
  • defer css/js load as much as possible
  • implement service worker