Best Practices for Writing Angular Apps

Best Practices for Writing Angular Apps



If you are using ngOnInit() method, you should implement onInit interface as well. It is by-default but for best practice we should add it.

export class AppComponent implements OnInit {
     ngOnInit(): void { }
}


Same as if you are using ngOnChanges() method, also implement onChanges interface as well.

export class AppComponent implements OnInit, OnChanges {
     ngOnInit(): void { }
     ngOnChanges(changes: SimpleChanges): void { }
}


All public method should be first and private method should be at the end of file.

{
     handleClick() { } // public method
     doPrevious() {}   // public method
     private calculateSpeed { }  // private method
}


When we declare any variable, we should take care of const, let and var.

const employeeId = 1;
let mobileNumber = 123456789;


We should use const wherever possible and than use let.


All variables declarations should be before constructor.

Bad Practice:
    constructor() {}
    employeeData: Array<IEmployee>;

Good Practice: 
    employeeData: Array<IEmployee>;
    constructor() {}


All methods, which are not used in HTML template or by other component or service, should be marked as private.


Declare variables as private if not used in HTML template.

private referenceNumber: number;
referenceName: string; // this is public, means it is used in html


Unused imports should be removed. You can use VS Code extension Typescript Hero. By this plugin you can also arrange imports automatically in a better way.

TypeScript Hero VS Code extension link: https://marketplace.visualstudio.com/items?itemName=rbbit.typescript-hero


Naming convention of variables and methods should be consistent. Stop to using short forms of variables.

var emp; > var employee; 
var mobileNo; > var mobileNumber;
private calcPrice() >  private calculatePrice();


Variables, functions, Parameters and methods type is must. Take care to not use of “any” type anywhere.

Bad Practice:
{
  City: any; 
  AreaType: any;
}

Good Practice:
{
  City: Array<String>;
  AreaType: IAreaType; // IAreaType is a predefined Interface.
}


Create Interfaces for each objects used in application.

export class Offer {
	offerDetails: IOfferDetails;
}

export interface IOfferDetails {
	offerId: number;
	offerType: string;
	offerValidity: boolean;
}


Create Models / Interfaces for each data model coming from API.

getProductDetails(id: number): Observable<IProduct[]> {
	return this.http.get<IProduct>('URL');
}


Enum is important for an application. So use enums where it’s need. Mostly we create an enum where multiple values exists for a property.

export enum AreaType {
        URBAN: 'URBAN',
        RURAL: 'RURAL'
}


Generally we create one enum file and put all enums into this same file. We suggest to create separate enum files for each Type. Enum values should be in caps letters.

Bad Way:
export enum Enum {
	MALE: 'MALE',
	FENAME: 'FENAME',
	PRIVATEOFFICE: 'PRIVATEOFFICE',
	PUBLICOFFICE: 'PUBLICOFFICE'
}

Good way:

export enum GenderType {
	MALE: 'MALE',
	FENAME: 'FENAME'
}
export enum OfficeType {
	PRIVATE: 'PRIVATE',
	PUBLIC: 'PUBLIC'
}


All configurations should be in one file like file name is AppConfigurations.

export class AppConfiguration {
	public static readonly isDisplayOffer: boolean = false;
	public static readonly isMegaSale: boolean = true;
        public statis readonly configURL: string = 'url';
}


Try to avoid unnecessary services files. You can create Base Classes, Models.

export class ShapeBase {
 // add common methods here
}

export class Circle extends ShapeBase { }

export class Line extends ShapeBase { }


If we need to create service file, It should be separated for each task. Don’t write every method or Logic in a single service file.


Folder structure of application
Create enum file, interfaces, models on Component level if those are not used in other components. Enums, Constants and Interfaces or Models which are used at multiple places, create those on root level only.

app >
        component >
            student
                > student.component.ts
                > student.component.HTML
                > student.component.cs
                > interfaces 
                    > student.interface.ts
                > enum 
                    > student-type.enum.ts
        interfaces >
            > college.interface.ts
            > category.interface.ts
        enums
            > class-type.enum.ts
            > dress-code-type.ts
        constants 
            > app-config.constant.ts


When work with router param. Take care of Path params and query params. Path params we use only to open a page & if we need to pass some data as well, use query params.

<a [routerLink]="['/products']" [queryParams]="{ code: 'newArrival', zipCode ="100302"}"> Products </a>

<a [routerLink]="['/products', {id: productId}]"> Products </a>


If working with child components and we are using input decorators, we should also include *ngIf conditions so that, that child component only load when input property is defined.

<app-report *ngIf = "reportData" [dataModel]= "reportData" [option]= "reportOptions"></app-report>


If there are some common methods, used in multiple classes than we should create a base class and write all common methods there & then extends that class by other classes. We also create a service file but Base class is a best practice.

export class ProductBase {
	constructor(protected service: ProductService ) {}
	
	ngOnInit() {
		this.preOrder();
	}

	protected preOrder() {
		// check product exist or not
	}

    protected calculatePrice(quantity: number, unitPrice: number, offer: number) {
         const price = quantity * unitPrice;
         return price - (price * offer / 100);
    }

}

export class ElectricProduct extends ProductBase {
    constructor(protected service: ProductService) {
        super(service);
	}
	
	// you can extend this method if need more functionality
	preOrder() {
		super.preOrder();
	}

}


Unit test cases of each method should be written. We should strict on code coverage area & it should be minimum of 80%.


Before going to commit code, Check you code by running command – ng test & ng build --prod.


All linting issues should be resolved before commit code. We can use tslint extension of visual studio code.

TSLint VS Code extension link:
https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin


All code formatting should be corrected before going to commit. We can use Beautify extension of visual studio code.

Beautify VS Code extension link:
https://marketplace.visualstudio.com/items?itemName=HookyQR.beautify


We should add comments above methods where actually required. If there are some logic in methods or something which can be used for debugging purpose, than put comments.


Use of async await instead of nested callbacks.

private async fetchData() {
      const code = await this.httpClient.get<any>('URL').toPromise();
      this.httpClient.get<any>('URL', {params: code});
  }


Remove commented blocks if that are not in any use for further coding.


If there is anything pending in code, we should add 'TODO' next to that statement / code, so that later on we can catch all todo and fixed that.

private getJobs() {
	const selectedDate: new Date(); // TODO: fetch date from date picker
	this.http.get('URL' + '?date=' + selectedDate);
}


When working with HTTP Headers, Create Custom HTTP interceptor & add common header config here only.

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthInterceptorService implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    req = req.clone({
      setHeaders: {
        Authorization: 'Bearer ' + 'your token'
      }
    })
    return next.handle(req);
  }
}


Error Handling should be done at global place.

import { Injectable, ErrorHandler } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlerService implements ErrorHandler {

  constructor() { }
  handleError(error: Error) {
    if (error) {
      console.error(error);
     }
  }
}

// app.module.ts

 providers: [{provide: ErrorHandler, useClass:  ErrorHandlerService}],


When working with HTTP request, add parameter by using HttpParams module instead of direct append in URL with ‘?’.

import { HttpClient, HttpParams } from '@angular/common/http';

 getData(code: string, id: string) {
    const httpParam = new HttpParams();
    httpParam.set('divisionCode', code);
    httpParam.set('divisionId', id);
    return this.httpClient.get('https://jsonplaceholder.typicode.com/todos1/1', { params: httpParam });
  }


We should use Array Map when pass multiple parameters in request params.
read here for Array Map
https://www.javatpoint.com/javascript-map-set-method

getProduct() {
    const parameter: Map<string, any> = new Map();
    parameter.set('productId', 1);
    parameter.set('type', 'Electric');
    parameter.set('validDate', new Date());
    parameter.set('isNew', false);
    this.getProductData(parameter);

  }

  getProductData(parameters: Map<string, any>) {
    let params = new HttpParams();
    parameters.forEach((value: any, key: string) => {
      params = params.append(key, value);
    });
    return this.httpClient.get('URL', { params: params });
  }


Always write active statement instead of passive means try to write True statement first then False statement.

var status = !isChecked ? 'inCompleted' : 'Completed';

Good way:
var staus = isChecked ? 'Completed' : 'InCompleted';


If working with Arrays, Check two conditions before executing logic.

a. Check it is exists or not
b. Check it’s length is greater then 0

var products = [];
if(products && products.length) {
// execute code logic
}


Arrow function – No need to put curly braces and return keyword if there is only single statement executing inside this.

var responseId = 2;
var items = [{id: 1, name: 'books'}, {id: 2, name: 'pen'}, {id: 3, name: 'tables'}];

var findItem = items.find((element) => { return element.id == responseId });

Good way:
var findItem = items.find((element) => element.id == responseId);


We should take care to write wrong spellings in our code. We can also use Visual Code extensions Code Spell Checker.

Spell checker Extension link: Click here


If there is a variable that is only aligned in html template, mark that protected. Mostly we create such variable as public but vs code gives you a warning like this variable is not used in further code, so it becomes confusing for programmer. To fix this, we mark such variable as protected.

export class AppComponent { 
  protected heading: string = 'This variable is only used in html template';
}



Read here top Typescript questions:
https://www.jsmount.com/typescript-basic-interview-questions-part-1/

Read Angular Style guidelines here…
https://angular.io/guide/styleguide

Best Practices for Writing Angular Apps | Guidelines book

Leave a Reply

Your email address will not be published.