fbpx Skip to main content

30 Angular tricks and tips to improve your application

Grzegorz Kruk

Angular shortens development time for applications, reducing the time it takes to deliver custom software. Improving your app’s performance is vital to staying competitive and profitable. Here are 30 Angular tips.

  1. Define form parameters before Component definition. 
    					
    
    
     
    export const FORM_PARAMS = {
     status: 'status',
     classification: 'classification',
     note: 'note'
    };
    
    
    
    @Component({
     selector: 'app-preview',
     templateUrl: './preview.component.html',
     styleUrls: ['./preview.component.scss']
    })
    
    
    
    this.form = this.formBuilder.group({
     [FORM_PARAMS.status]: [false],
     [FORM_PARAMS.classification]: [null],
     [FORM_PARAMS.note]: [null, Validators.required]
    });
    
    
    
    this.form.patchValue({ [FORM_PARAMS.status]: 'status' });
    

     

  2. Use renderer addClass, removeClass where possible. It will prevent too many change detection checks. 
    					
    
    
     
    // SLOWER
    collapseRow(rowIndex: number) {
     this.setRowCollapsed(rowIndex);
    }
    
    
    // FASTER
    collapseRow(event, rowIndex: number) { 
      this.setRowCollapsed(rowIndex);
      this.render.removeClass(event.target, 'arrowDown');
      this.render.addClass(event.target, 'arrowRight');
    }

  3. Try to use get, set on Input() instead ngOnChanges. When you have many Inputs in the ngOnChanges, each “if” has to be checked. 
    					
    
    
     
    // SLOWER when many ifs
    ngOnChanges(changes: SimpleChanges) {
     const data: SimpleChange = changes.data;
     if (data && data.currentValue) {
       this.data = [...data.currentValue];
     }
     if (configuration && configuration.currentValue) {
       this.config = configuration.currentValue;
     }
    }	
    
    
    
    // FASTER
    
    
    
    public _config: Config;
    @Input('configuration')
    set configuration(value: Config) {
     this._config = value;
    }
    
    
    
    get configuration(): Config {
     return this._config;
    }
    
    
    
    _data: Data;
    @Input('data')
    set data(value: Data) {
     this._data = [...value];
    }
    
    
    
    get data(): any[] {
     return this._data;
    }
    

     

  4. Use pipe when rendering content in ngFor. 
    					
     
    // Slower
     {{ render(row, column) }}
    
    render(row: any, column: string) {
     return YourService.render(row, column);
    }
    
    
    
    // Faster
     {{ row | render:column }}
    
    
    
    
    @Pipe({
     name: 'render',
    })
    export class RenderPipe implements PipeTransform {
     transform(row: any, column: string) {
       return YourService.render(row, column);
     }
    }
    

  5. Specify baseUrl and module aliases (paths) to your compilerOptions to avoid any inconsistency when importing other files.
    					
    
    
     
    {
     "compilerOptions": {
       "baseUrl": "src",
       "paths": {
         "@core/*": ["app/*"],
         "@assets/*": ["assets/*"]
       }
     }
    }
    
    
    
    import { Recruitment } from '@core/domain/recruitment';
    
    
    
    instead
    
    
    
    import { Recruitment } from '../../../domain/recruitment';
    

     

  6. Add stylePreprocessorOptions to your angular.json file to avoid inconsistency while importing other files. 
    					
    
    
     
     "projects": {
       "project-frontend": {
         "root": "",
         "sourceRoot": "src",
         "projectType": "app",
         "architect": {
           "build": {
             "builder": "@angular-devkit/build-angular:browser",
             "options": {
               "stylePreprocessorOptions": { // <--- add this
                 "includePaths": [
                   "./src/assets/style/scss"
                 ]
               }
             }
           }
         }
       }
      
     @import "variables";
      
     instead
    
    
     @import "../../assets/style/scss/variables";
    

     

  7. Run npm outdated command or add npm-check once a month to keep your dependencies updated. It will definitely help you keep track of changes. It’s much easier to update Angular 5 to 6 than 4 to 6.
  8. Run npm audit command once a month to check if any of the libraries has any vulnerabilities. It will help you keep your app secure. 
    					
    
    
     
    if [[ $(npm audit | grep Critical -B3 -A10) != '' ]]; then exit 1; fi"
    

     

  9. Use parent in form validation instead of this.form which may not be initialised while doing/performing custom validation check. 
    					
    
    
     
    // Correct 
    
    
    
    static validateEndDate(fc: FormControl) {
     const startDate = fc.parent.get(FORM_PARAMS.startDate);
     if (startDate.value) {
       const diff = fc.value - startDate.value;
       return (diff < 86400) ? { endDateInvalid: true } : null;
     }
     return null;
    }
    
    
    
    // Incorrect 
    
    
    
    static validateEndDate(fc: FormControl) {
     const startDate = this.form.get(FORM_PARAMS.startDate);
     if (startDate.value) {
       const diff = fc.value - startDate.value;
       return (diff < 86400) ? { endDateInvalid: true } : null;
     }
     return null;
    }
    

     

  10. Keep route names as const. It will prevent accidental typos. 
    					
    
    
     
    export class ROUTE {
     public static readonly LOGIN = '/login';
     public static readonly RECRUITMENTS = '/recruitments';
     public static readonly RECRUITMENT_EDIT = '/recruitments/:id';
    }
    
    
    
    goToRecruitment($event, id: number) {
     $event.preventDefault();
     this.router.navigate([ROUTE.RECRUITMENT_EDIT.replace(':id', id)]);
    }
    

     

  11. Start using webpack-bundle-analyzer. It will help you detect any fast-growing modules.In our case by mistake main.scss has been included in another file instead variable.scss. It has doubled the size of the bundle! 
    					
    
    
     
    "scripts": {
       "bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
    },
    93f72404-b338-11e6-92d4-9a365550a701.gif
    Gif Source: https://github.com/webpack-contrib/webpack-bundle-analyzer
    

     

  12. Use browser Performance tests. 17ms rendering time means you use 60fps. Actions with fewer than 60fps and more than 30fps are ok. Any speed below 30fps makes the user notice the visualized slowdown of the application.
  13. Use the Augury chrome extension to track the current state of components.
  14. Prettier as code formaterTo prevent conflicts between tslint and prettier use the npm tool
    					
    
    
     
    // .prettierrc
    {
     "printWidth": 140,
     "parser": "typescript",
     "tabWidth": 2,
     "singleQuote": true,
     "trailingComma": "all"
    }
    

     

  15. Use trackBy in ngFor loops to optimize the re-rendering of iterable.trackByFn is a  function that defines how to track changes for items in the iterable. When items are added, moved or removed in the iterable, only those nodes that have changed are re-rendered.
    Example: 
    					

    @Component({
     template: `
    • {{item.id}}
     `,
    })
    export class SampleComponent {
     constructor() {
       this.items = [
         {id: 1},
         {id: 2},
         {id: 3}
       ];
    

  16.  Use a virtualized scroll list like e.g. CDK Virtual Scroll when you need to display a very large records collection. It will render just items that fit within ViewPort in the current scroll position, in comparison to rendering all items at once without virtualization.
    					

    import { ScrollingModule } from '@angular/cdk/scrolling';
     
    @NgModule({
     ...,
     imports: [
       ...,
       ScrollingModule
     ],
     ...,
    })
    export class AppModule { }
     
    @Component({
     template: `
    • {{item.id}}
     `,
    })
    export class SampleComponent {
     constructor() {
       for (let index = 0; index < 10000; index++) {
         this.items.push({ id: index });
       }
     }
     trackByFn(index, item) {
       return item.id;
     }
    }
    

  17. From Angular v9+, use ngZoneEventCoalescing flag to reduce the amount of change detection cycles while Event Bubbling.
    					

    platformBrowserDynamic()
     .bootstrapModule(AppModule, { ngZoneEventCoalescing: true })
     .catch(err => console.error(err));


    Example:
    					

    platformBrowserDynamic()
     .bootstrapModule(AppModule, { ngZoneEventCoalescing: true })
     .catch(err => console.error(err));
    

    Normally when we click on the button element, both “childrenFn” and “parentFn” are invoked, causing two change detection cycles. With ngZoneEventCoalescing flag set to true, there is only one change detection cycle to run. 

  18. Debugging Angular component in console
    1. Open browser developer tools.
    2. Select element in “Elements” tab
    3. Debug this element in console: ($0 – in this variable is selected element)

    Before Angular v9:

    					

    const selectedComponent = ng.probe($0).componentInstance;
    selectedComponent.value = "New value";
    ng.probe($0).injector.get(ng.coreTokens.ApplicationRef).tick();

    From Angular v9+:

    					

    const selectedComponent = ng.getComponent($0);
    selectedComponent.value = "New value";
    ng.applyChanges(myComponent);
    

    From Angular v9+ it is even better to use this SB Angular Inspector.

  19.  Lazy loading in Angular – instead of loading a complete app, Angular loads only the modules which are required at the moment. It reduces the initial load time. 
  20. Always keep an eye on your Bundle Size. Depends on the scale of your project, in enterprise apps if your main.*.js file exceeds more than 0.5 MB, you may need to be aware. After your Angular app is deployed check if your cloud platform or CDN, hosts your bundles gzipped. In the network tab in the developer console in Response Headers, you should see the header: “Content-Encoding: gzip.” If you have your own server serving your app and it doesn’t implement gzip compression, you should definitely add those. 
  21. While you don’t need to validate or react on every single change of your FormControl, use updateOn: ‘blur’. Event callbacks will be executed on blur events. 
    					
    
    
    this.email = new FormControl(null, { updateOn: 'blur' });

     

  22. Use Services for any Side Effects.It is a good place for any  HTTP requests, event handlers, time-based events. It reduces the complexity of the component and provides reusability to our codebase. 
    					
    
    
    @Injectable({
     providedIn: 'root'
    })
    export class UsersService {
     constructor (private http: HttpClient) {}
     
     getUsers() {
       return this.http.get(API_ENDPOINT);
     }
    }
      
    @Component({
     selector: 'app-component',
     template: '
    • {{item}}
    ',
    })
    export class AppComponent implements OnInit{
     constructor(private usersService: UsersService){
     }
     items = [];
      ngOnInit(){
       this.usersService.getUsers().subscribe(items => this.items = items);
     }
    }
    

  23. Use the Angular CLI tool that makes it easy to create your application. With just one command you can generate whatever you want, use analytics, sets project configuration, and many more. Here are some useful commands.

    To generate component under desired path with sass style files: 
    					

    ng generate component components/yourComponentName --style=sass
     
    Abbreviation:
    ng g c components/yourComponentName --style=sass
    

    To see what files will be generated without creating them use flag: 

    					

    ng g c components/yourComponentName --dryRun=true

    To generate without tests files: 

    					

    ng g c components/yourComponentName --skipTests=true

     

  24. From Angular 6 on, with the help of @angular/elements package you can create custom native components (Web Components) that can be used outside of the Angular. This package provides polyfills to support browsers that don’t support Web Components.To get started, install the package in an Angular project with the following command: 
    					
    
    
    ng add @angular/elements --name=

     

  25. Use an ErrorHandler from @angular/core to handle errors in your app. With this dedicated service, you can easily manage errors in your entire application. As a default, the service catches the errors and logs them to the console, but it can be extended by additional side effects, whatever you need. It is pretty easy to implement such a service: Simply create a file called e.g. error-handler. 
    					
    
    
    import { ErrorHandler } from '@angular/core';
     
    class MyErrorHandler implements ErrorHandler {
     handleError(error: any) {
       // do something with the exception
       super.handleError(error);
     }
    }
     
    @NgModule({
     providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
    })
    class MyModule {}
    

     

  26. Add Aliases support to your project. If you think importing your components is ugly, try this. 
    					
    
    
    import { MyComponent } from '../../../../components/parent-component/my-component'

    Specify aliases to your project folders in tsconfig.json file: 

    					

    {
     "compilerOptions": {
       "baseUrl": "src",
       "paths": {
         "@components": "app/components",
         "@services": "app/services"
       }
     }
    }
    

    And import your code in this way: 

    					

    import { MyComponent } from '@components/parent-component/my-component';

  27. Preload your resources and routes to speed up your application.

    Angular has a preloading strategy implemented already in @anuglar/router module. It allows us to preload routes/links but also resources, modules etc. This process speeds uploading and rendering time of the resources, by preloading them to a browser cache. 

    					

    class CustomPreloadingStrategy implements PreloadingStrategy {
     preload(route: Route, fn: ()=> Observable) {
         // ...
     }
    }
     
    RouterModule.forRoot([
     ...
    ], {
     preloadingStrategy: OurPreloadingStrategy
    })
    


  28. Do you have a heavy computational function that slows your application down? From default JavaScript is a single-threaded language, so your heavy computations runs along with UI updates, but there is a remedy for it. With help of the Web Workers you can move your heavy non-UI algorithm to run on separate non-UI-blocking thread.Create file with your custom Web Worker (‘./myWebWorker.js’): 
    					
    
    
    onmessage = function(e) {
     console.log('Worker: Message received from main script');
     const result = e.data[0] * e.data[1];
     if (isNaN(result)) {
       postMessage('Please write two numbers');
     } else {
       const workerResult = 'Result: ' + result;
       console.log('Worker: Posting message back to main script');
       postMessage(workerResult);
     }
    }
    

    Then in your component subscribe on it: 

    					

    @Component({
     selector: 'app',
    })
    export class App implements OnInit{
     private output
     private webworker: Worker
     ngOnInit() {
         if(typeof Worker !== 'undefined') {
             this.webWorker = new Worker('./myWebWorker')
             this.webWorker.onmessage = function(data) {
                 this.output = data
                 console.log('Message received from worker');
             }
         }
     }
     runHeavyAlgorithm(firstValue: number, secondValue: number) {
         this.webWorker.postMessage([firstValue, secondValue])
     }
    }
    


  29.  Use Pure PipesTo specify that your Pipe as “Pure” simply just set the “pure” flag in its configuration: 
    					
    
    
    @Pipe({
     name: "units",
     pure: true
    })
    class UnitsPipe implements PipeTransform {
    ...
    }
    

    In this way any inputs passing through this Pipe is computed only once and cached, next calls with the same inputs will be skipped and returned the cached data.

  30. Remove all caret “^” and tilde “~” from your package.json. If you want your project to be stable and not to fail in the future you should stick to specific versions of packages you use.