Optimizing Angular apps
Disclaimer
This is a set of tips I've learned from people way smarter than me
Not as a call for a revolution in our apps
Treat this as opportunity to supplement your knowledge
🤯
🤔
⚔️
General
Angular
General
Managing your NPM packages
Source: https://www.youtube.com/watch?v=M3BM9TB-8yA
(10 Things I Regret About Node.js - Ryan Dahl)
Review your dependencies
source-map-explorer
Check the size first
Deduplicate
Not everything belongs to "dependencies"
Keeping bundle size in check
(Tree) Shake It!
General
JavaScript
Generally, do what you always do
- use the algorithms with the least computational complexity
- avoid recursive calls
- put the calculations and calls to functions that are repeated to variables
- etc.
Beware fancy code
var numbers = [2, 4, 12, 6, 8, 29, 5, 10, 87, 11, 7];
function process(arr) {
let newArr = arr.slice();
newArr[0]++;
for (let i = 1; i < newArr.length; i++) {
const current = newArr[i] + 1;
let leftIndex = i - 1;
while (leftIndex >= 0 && newArr[leftIndex] > current) {
newArr[leftIndex + 1] = newArr[leftIndex];
leftIndex = leftIndex - 1;
}
newArr[leftIndex + 1] = current;
}
return newArr;
}
const newArray = process(numbers);
Beware fancy code
const process = arr => arr
.map(num => num + 1)
.sort((a, b) => a - b);
const newArray = process(numbers);
... which is 75% slower
than the previous solution.
Web Workers
// web-worker.worker.ts
addEventListener('message', ({ data }) => {
const val = someHeavyOperation();
postMessage(val);
});
// some.component.ts
startWebWorker() {
if (typeof Worker !== 'undefined') {
const worker = new Worker(
'./web-worker.worker',
{ type: 'module' }
);
worker.onmessage = ({ data }) => {
// Hide loading widget
this.loading = false;
};
// Display loading widget
this.loading = true;
worker.postMessage(this.dataSize);
}
}
Profiling Tools: Chrome's Lighthouse
Profiling Tools: Node Clinic
Angular
"Optimizing an Angular application" - Minko Gechev
Minko's tips
- keep in mind the Angular's change detection (use ChangeDetectionStrategy.OnPush and separate your components)
- use immutable data structures (Immer, Immutable.js )
- use pure pipes instead invoking function in a template
- cache outputs (memoizing)
- think about an optimal strategy for refreshing data from a server
Lazy-loading modules
const routes: Routes = [
{
path: 'customers',
loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule)
},
{
path: 'orders',
loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule)
}
];
Use trackBy in *ngFor
@Component({
selector: 'app',
template: `
<ul>
<li *ngFor="let item of items; trackBy: trackById">
{{item.name}}
</li>
</ul>`
})
class AppComponent {
items = [
{
id: 1,
name: 'item 1'
},
{
id: 2,
name: 'item 2'
}
];
trackById(index, item) {
return item.id;
}
}
Don't leave your Observables subscribed or Promises pending
@Component(...)
class AppComponent implements OnInit, OnDestroy {
customers$ = new Subject<Customer[]>();
sub = new Subscription();
...
ngOnInit() {
this.sub = this.customers$.subscribe(...);
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
Don't manipulate DOM directly or use addEventListener
@Component({
selector: 'app-click-me',
template: `
<button #click-me-btn>Click me!</button>
{{clickMessage}}
`
})
export class ClickMeComponent {
clickMessage = '';
ngOnInit() {
const el = document.getElementById('click-me-btn'); // ⚠️ #1
el.addEventListener('click', event => { // ⚠️ #2
this.onClickMe();
el.style.color = 'green'; // ⚠️ #3
console.log('Clicked!'); // ⚠️ #4
});
}
onClickMe = () => {
this.clickMessage = 'You are my hero!';
}
}
- addEventListener may result in memory leaks
- direct DOM manipulation is slow and inefficient (Angular uses virtual DOM)
- even using console.log() slows down the app!
Leveraging the NgZone
@Component(...)
export class DragAndDropComponent {
element: HTMLElement;
constructor(private zone: NgZone) {}
mouseDown(event) {
this.element = event.target;
this.zone.runOutsideAngular(() => {
window.document.addEventListener('mousemove', this.mouseMove);
});
}
mouseMove = (event) => {
event.preventDefault();
this.element.setAttribute('x', event...);
this.element.setAttribute('y', event...);
}
mouseUp(event) {
this.zone.run(() => {
this.updateBox(...);
});
window.document.removeEventListener('mousemove', this.mouseMove);
}
}
Progressive Web Apps
Angular Ivy compiler
- 🚀 better build times (with a more incremental compilation)
- 🔥 better build sizes (with a generated code more compatible with tree-shaking)
- 🔓 new potential features
Final, general tips
- compress/transform your images and static files
- remove unused CSS
Thank you!
✋😎
@kajetansw
kajetan.dev
Optimizing Angular app
By Kajetan Świątek
Optimizing Angular app
- 332