Skip to content

Instantly share code, notes, and snippets.

@jbsummoner
Last active October 20, 2021 15:20
Show Gist options
  • Save jbsummoner/59bd32a095cef545f181e0e29a247979 to your computer and use it in GitHub Desktop.
Save jbsummoner/59bd32a095cef545f181e0e29a247979 to your computer and use it in GitHub Desktop.
checklist for angular best practices

Checklist - WIP

Here’re some set of rules that you need to follow to make your project comply with the standard Angular style guide

  • Single Responsibility Principle, check
  • Per file, the code must not exceed from 400 lines limit
  • Per function, the code must not exceed from 75 lines
  • Utilise custom prefix to prevent element name collisions with components in other apps and with native HTML elements.
  • If the values of the variables are intact, declare it with const
  • Names of properties and methods should be in lower camel case
  • Always leave one empty line between imports and module such as third party and application imports and third-party module and custom module
  • We shouldn’t name our interfaces with the starting capital I letter as we do in some programming languages.
  • Follow naming conventions
  • Proper Observable handling, check
  • Utilize index.ts, check
  • Proper change detection practice, check
  • use trackBy with ngFor, check
  • check for public, protected, private modifiers to make an appropiate api for class, funtions, etc
  • handle all subscriptions..subsink or rxjs Subscription

Naming Convention Checklist

  • Classes
    • UpperCamelCase
  • Files i.e .component.ts, component.html, component.scss, .module.ts, .pipes.ts, .routing.ts
  • Interfaces
    • UpperCamelCase
    • suffix with 'I' I.E UserI, InstrumentListI
  • Observables

Single Responsibility Principle

It is very important not to create more than one component, service, directive… inside a single file. Every file should be responsible for a single functionality. By doing this, we are keeping our files clean, readable, and maintainable.

File Naming

While creating files, we should pay attention to the file names.

Names of folders and files should clearly convey their intent. Names should be consistent with the same pattern in which we mention the file’s feature first and then the type, dot separated.

For example consultation.component.ts or home.component.html or auth.service.ts

If we want to add more descriptive names to our files we should use a dash(-) to separate the words in the name: tc-home.component.ts book-appointment.component.ts

Using Classes

When we add names to classes, we should use upper camel case style with the added suffix that represents the type of our file: DatepickerDirective TcHomeComponent AuthService

Angular CLI

The Angular CLI is a command-line interface tool that is used to initialize, develop, scaffold, maintain, and even test and debug Angular applications.

ng new - To create an application that already works, right out of the box. ng generate - To Generate components, routes, services and pipes with a simple command with test shells. ng serve - To test your app locally while developing. ng test - To run your unit tests or end-to-end tests ng lint - To make your code shine ng add @angular/pwa - To set up the Angular service worker

This interface can be used to create an initial-level structure for the application. It’d be much easier for developers to understand the folder structure and app flow using Angular CLI. Ultimately, it saves hours of developers time.

Folder Structure

--app
  |-- modules
    |-- feature1
      |-- [+] components
        |-- [+] atoms
          # basic element structure 
          |-- atom1
            |-- atoms1.component.ts
            |-- atoms1.component.scss //optional, should we move styles to styles dir?
            |-- atoms1.component.html 
        |-- [+] molecules
        |-- [+] organisms
        |-- [+] pages
        |-- [+] templates
      |-- [+] core
        |-- [+] types 
        |-- [+] pipes 
        |-- [+] directives 
      |-- [+] services
      |-- feature1-routing.module.ts
      |-- feature1.module.ts
      |-- feature1.component.ts
      |-- feature1.component.scss //optional, should we move styles to styles dir?
      |-- feature1.component.html 
    |-- feature2
      |-- [+] components
      |-- [+] core
        |-- [+] types 
        |-- [+] pipes 
        |-- [+] directives 
      |-- [+] services
      |-- feature2-routing.module.ts
      |-- feature2.module.ts
  |-- core
    |-- [+] components
      |-- [+] header
      |-- [+] footer
    |-- [+] gaurds
    |-- [+] interceptors
    |-- [+] mocks
    |-- [+] services
    |-- [+] authentication
    |-- core.module.ts
  |-- shared
    |-- [+] components
    |-- [+] directives
    |-- [+] pipes
    |-- [+] core
    |-- [+] types 
    |-- [+] pipes 
    |-- [+] directives 
    |-- [+] configs
|-- assets
|-- styles
|-- [+]  partials
|-- _base.scss
|-- styles.scss

Atoms & Molecules — Dumb components

They are simple, reusable, and easy to test pieces of code. They both receive @Input and can emit @Ouput. The perfect candidates for UI libraries.

Atoms: The smallest unit possible, reused all over the place. Normally a single HTML element with basic styling. Molecules: A single group of atoms.

Organisms & templates — Simplifying smart components

In terms of reducing smart components complexity, templates help reducing the HTML boilerplate, and organisms their typescript counterpart. Templates and organisms can help reduce the noise, and keep the code structured and ready to scale. It will be easier to recognize and reuse the pieces of code in future similar-looking features. Widgets. Stand-alone sections of the app that may be reused among multiple pages and have self-contained meaning.

Templates

Think of templates as scaffold & skins for a certain component. Just plain HTML-CSS with ng-content. They are used by high order components (like pages or organisms) to take care of style variability and HTML boilerplate. This component shouldn´t have any input, output, logic, or constructor dependencies. image

Pages — Smart components

Commonly referred to as features in the classic core/features/shared project structure. Smart components manage input/output connecting with the core of your app vía services.

Using Interfaces

If we want to create a contract for our class we should always use interfaces. By using them we can force the class to implement functions and properties declared inside the interface.

The best example for this is to have angular life cycle hooks in your component: class HomeComponent implements OnInit, OnDestroy

Using interfaces is a perfect way of describing our object literals. If our object is an interface type, it is obligated to implement all of the interface’s properties.

export interface UserI {
  name: string;
  age: number;
  address?: string;
};

Using Immutability

Objects and arrays are the reference types in javascript. If we want to copy them into another object or an array and to modify them, the best practice is to do that in an immutable way using es6 spread operator(…)

const person: UserI = {
  name: 'bob',
  age: 40
};

const updatedPerson = {
  ...person,
  name: 'alice'
};

//or

const updatedPerson = Object.assign(person, { name: 'alice'});

Safe Navigation Operator (?)

To be on the safe side we should always use the safe navigation operator while accessing a property from an object in a component’s template. If the object is null and we try to access a property, we are going to get an exception. But if we use the save navigation (?) operator, the template will ignore the null value and will access the property once the object is not null anymore.

<span>{{ user?.name }} </span>

Prevent Memory Leaks in Angular Observable

Observable memory leaks are very common and found in every programming language, library, or framework. Angular is no exception to that. Observables in Angular are very useful as it streamlines your data, but memory leak is one of the very serious issues that might occur if you are not focused. It can create the worst situation in mid of development. Here’re some of the tips which follow to avoid leaks. suffix variable with '$'

const  protected state$: Observable<T>;

Using async pipe Using take(1) Using takeUntil() Unsubsribe from subscriptions in ngOnDestroy

Using index.ts

index.ts helps us to keep all related things together so that we don’t have to be bothered about the source file name. This helps reduce the size of the import statement.

For example, we have article/index.ts as

export * from './user.model';
export * from './user.config';
export { UserComponent } from './user.component';

We can import all things by using the source folder name.

export { UserComponent, UserConfig } from './user';

Change Detection Optimisations

  1. Use NgIf and not CSS - If DOM elements aren’t visible, instead of hiding them with CSS, it's a good practice to remove them from the DOM by using *ngIf.
  2. Move complex calculations into the ngDoCheck lifecycle hook to make your expressions faster.
  3. Cache complex calculations as long as possible
  4. Use the OnPush change detection strategy to tell Angular there have been no changes. This lets you skip the entire change detection step.

Build Reusable Components

If there is a piece of UI that you need in many places in your application, build a component out of it and use the component. This will save you a lot of trouble if, for some reason, the UI has to be changed. In that case, you do not go around changing the UI code in all the places. Instead, you can change the code in the component and that is it.

For this, you may have to use property and event bindings to pass inputs to the components and receive output events from the components, respectively.

Using trackBy in NgFor

When using ngFor to loop over an array in templates, use it with a trackBy function which will return a unique identifier for each DOM item.

When an array changes, Angular re-renders the whole DOM tree. But when you use trackBy, Angular will know which element has changed and will only make DOM changes only for that element.

<ul>
  <li> *ngFor="let item of itemList; treackBy: trackByFn"
    {{item.id}}
  </li>
</ui>

Module Organisation and Lazy Loading

Utilizing lazy load the modules can enhance productivity. Lazy Load is a built-in feature in Angular which helps us with loading the things on demand. When have used LazyLoad it helps in reducing the size of the application by abstaining from unnecessary file from loading. Following are the modules, which are been used in angular apps widely

Multi Modules
Routing Modules
Shared Modules
Lazy Load Modules

Always Document

Always document the code as much as possible. It will help the new developer involved in a project to understand its logic and readability.

It is a good practice to document each variable and method. For methods, we need to define it using multi-line comments on what task the method performs and all parameters should be explained.

references

https://blogs.halodoc.io/angular-best-practices/ https://medium.com/weekly-webtips/angular-clean-arquitecture-d40e9c50f51 https://angular.io/guide/styleguide#angular-coding-style-guide

Other
  • like adding a styles prop to components for dynamic composable styling
<app-index-trader-weight-molecule
  *ngFor="let config of slidersConfigs; let i = index"
  [inputConfig]="config"
  [styles]="{
    label: {
      display: 'none'
    },
    legend: {
      display: 'none'
    }
  }"
  (onChange)="templateEventHandler($event)"
></app-index-trader-weight-molecule>
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-index-trader-selection-view',
templateUrl: './index-trader-selection-view.component.html',
styleUrls: ['./index-trader-selection-view.component.scss']
})
export class IndexTraderSelectionViewComponent implements OnInit {
/****************************
* Input / Output Bindings
*****************************/
@Input() private templateData;;
@Output() private updateTemplateData = new EventEmitter();
/****************************
* View Childs
*****************************/
// TODO: add viewchilds if applicable
/****************************
* Class Properties
*****************************/
/**
* @property destroy$
* @description BehaviorSubject to signal component destruction
*/
private destroy$ = new BehaviorSubject<boolean>(false);
/****************************
* Constructor
*****************************/
constructor() {}
/****************************
* Lifecyle Methods
*****************************/
ngOnInit() {}
ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.complete();
}
ngAfterViewInit() {}
ngOnChanges(changes: SimpleChanges) {}
/****************************
* Setters
*****************************/
/****************************
* Getters
*****************************/
/****************************
* Functions
*****************************/
/****************************
* Utils
*****************************/
}
<!-- https://stackoverflow.com/a/48515379 -->
<ul>
<li *ngFor='let link of links'>
<ng-container
[ngTemplateOutlet]="link.type == 'complex' ?complexLink : simpleLink"
[ngTemplateOutletContext]="{link:link}">
</ng-container>
</li>
</ul>
<ng-template #simpleLink let-link='link'>
Simple : {{ link.name }}
</ng-template>
<ng-template #complexLink let-link='link'>
Complex : {{ link.name }}
</ng-template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment