Common Practices That Kill Performance in Angular Applications
Developing high-performance Angular apps requires avoiding common pitfalls. Here are key practices that harm performance and how to fix them.
Developing high-performance applications with Angular requires attention to details that are often overlooked. Here are some common practices that can kill the performance of your Angular application, along with examples of bad code and their corrections.
1. Excessive Change Detection
Bad Code:
Change detection is triggered too frequently, affecting performance.
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
})
export class MyComponent {
@Input() data: any;
}
Correction:
Use the OnPush
strategy in components where possible. This makes change detection occur only when the component's input changes.
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
@Input() data: any;
}
2. Inefficient DOM Manipulation
Bad Code:
Direct DOM manipulations can be inefficient and hard to manage.
ngAfterViewInit() {
document.getElementById('elementId').style.color = 'blue';
}
Correction:
Use Angular’s Renderer2
to manipulate the DOM more efficiently.
import { Renderer2 } from '@angular/core';
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
const element = this.renderer.selectRootElement('#elementId');
this.renderer.setStyle(element, 'color', 'blue');
}
3. Overuse of Pipes
Bad Code:
Pipes that perform heavy operations can be re-executed frequently, hurting performance.
@Pipe({
name: 'heavyPipe',
pure: false
})
export class HeavyPipe implements PipeTransform {
transform(value: any): any {
// Heavy transformation logic
}
}
Correction:
Use pure pipes (pure: true
) for lightweight operations or avoid pipes for heavy operations.
@Pipe({
name: 'heavyPipe',
pure: true
})
export class HeavyPipe implements PipeTransform {
transform(value: any): any {
// Light transformation logic
}
}
4. Excessive Initial Load
Bad Code:
Loading all modules and components at initialization can delay loading time.
@NgModule({
declarations: [AppComponent, FeatureComponent],
imports: [BrowserModule, FeatureModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Correction:
Use lazy loading for modules that are not immediately needed.
const routes: Routes = [
{ path: 'feature', loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule) }
];
5. Unmanaged Subscriptions
Bad Code:
Not cancelling subscriptions can lead to memory leaks.
ngOnInit() {
this.myService.myObservable.subscribe(value => {
// Do something with value
});
}
Correction:
Use the async
pipe or manage subscriptions with takeUntil
or Subscription
.
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnDestroy {
private destroy$ = new Subject<void>();
constructor(private myService: MyService) {
this.myService.getData()
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
// Handle the data
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
For Angular 16+ use takeUntilDestroyed
.
import { Component, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnInit, OnDestroy {
constructor(private myService: MyService) {
}
ngOnInit(): void {
this.myService.getData()
.pipe(takeUntilDestroyed(this))
.subscribe(data => {
// Handle the data
});
}
ngOnDestroy() {
}
}
6. Excessive Dependencies in Providers
Bad Code:
Registering too many providers in the root module increases the bundle size.
@NgModule({
providers: [MyService]
})
export class AppModule {}
Correction:
Register providers only where they are necessary.
@NgModule({
providers: [MyService] // Register only where necessary
})
export class FeatureModule {}
7. Lack of AOT Compilation
Bad Code:
Using JIT (Just-in-Time) compilation can be less efficient.
// No specific configuration for AOT
Correction:
Configure the application to use AOT (Ahead-of-Time) compilation.
{
"projects": {
"my-app": {
"architect": {
"build": {
"options": {
"aot": true
}
}
}
}
}
}
8. Lack of Bundle Optimization
Bad Code:
Unoptimized bundles increase loading time.
// Default Angular CLI configuration
Correction:
Use tools like Webpack and Angular CLI build configurations to optimize bundles.
{
"projects": {
"my-app": {
"architect": {
"build": {
"configurations": {
"production": {
"optimization": true,
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
}
}
}
}
}
Conclusion
By following these practices and corrections, you can significantly improve the performance of your Angular application, making it more efficient and responsive.
Follow me on LinkedIn: https://www.linkedin.com/in/erickzanetti