DevUI is a team with both design and engineering perspectives, serving huawei DevCloud platform and huawei internal background systems, as well as designers and front-end engineers. Ng Component Library: Ng-Devui (Welcome Star)

The introduction

As one of the top three front-end frameworks, the Angular framework has unique advantages for creating efficient, complex, and sophisticated single-page applications.

This article introduces eighteen recommended best practices and examples for Angular development.

1. trackBy

What

When using the *ngFor directive to lay out arrays in HTML, add the trackBy() function to specify a separate ID for each item

Why

Typically, Angular rerenders the entire DOM tree when there are changes in the array. If you add the trackBy method, Angular will know about the changed element and DOM refresh that particular element to improve page rendering performance

For details, choose NetanelBasal

Example

[Before]

<li *ngFor="let item of items;" >{{ item }}</li>Copy the code

[After]

// in the template
<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>

// in the component
trackByFn(index, item) {
  return item.id; // unique id corresponding to the item
}
Copy the code

2. const vs let

What

When declaring constants, use const instead of let

Why

A. Make assignment intent more explicit

B. If the constant is reassigned, the compiler will report an error directly to avoid potential risks

C. Increase code readability

Example

[Before]

let car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
  myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
  yourCar = `${youCar}s`;
}
Copy the code

[After]

// Car will not be reassigned, so const car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) { myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) { yourCar = `${youCar}s`; }Copy the code

3. Pipeable operator

What

When using RxJs, use pipeable to manipulate the symbol -> expand reading

Why

A. Tree shakable: import code that needs to be executed is imported

B. Easy to locate unused operators in code

Note: RxJs version 5.5 and above is required

Example

[Before]

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

iAmAnObservable
.map(value => value.item)
.take(1)
Copy the code

[After]

import { map, take } from 'rxjs/operators';

iAmAnObservable
.pipe(
  map(value => value.item),
  take(1)
)
Copy the code

4. Isolate API attacks

What

Not all apis are secure -> In many cases additional code logic is added to patch the API

Rather than putting this logic in a component, it is better to encapsulate it in a separate place, such as a service, and reference it elsewhere

Why

A. Isolate attacks so that they are closer to the original request location

B. Reduce the number of code used to deal with attack patching

C. Encapsulate these attacks in one place, making them easier to detect

D. When you want to solve a bug, you only need to search in the same file, which is easier to locate

Note: You can also use a proprietary tag, such as API_FIX, similar to the TODO tag, to mark API fixes

5. Subscription to templates

What

It is better to subscribe to changes in HTML than in TS

Why

A. Async pipeline can automatically unsubscribe: simplifies code by reducing manual subscription management

B. Reduce the risk of memory leaks caused by forgetting to unsubscribe in TS (this risk can also be avoided by lint rule detection)

C. Reduce the introduction of bugs due to data changes outside the subscription

Example

[Before]

// template
<p>{{ textToDisplay }}</p>

// component
iAmAnObservable
.pipe(
  map(value => value.item),
  takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item
Copy the code

[After]

// template
<p>{{ textToDisplay$ | async }}</p>

// component
this.textToDisplay$ = iAmAnObservable
.pipe(
  map(value => value.item)
)
Copy the code

6. Subscribe to cleanup

What

If you subscribe to an Observable, unsubscribe properly using the take, takeUntil, and other operators

Why

A. Not unsubscribing can cause memory leaks by observing observable flows that persist even when the component is destroyed or the user goes to another page

B. Better yet: avoid it with lint rule checking

Example

[Before]

iAmAnObservable
.pipe(
  map(value => value.item)
)
.subscribe(item => this.textToDisplay = item);
Copy the code

[After]

private _destroyed$ = new Subject(); public ngOnInit (): Void {iamanobservable. pipe(map(value => value.item) // takeUntil(this._destroyed$)).subscribe(item => this.textToDisplay = item); } public ngOnDestroy (): void { this._destroyed$.next(); this._destroyed$.complete(); }Copy the code

If you only want the first value, use a take(1).

iAmAnObservable
.pipe(
map(value => value.item),
take(1),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
Copy the code

Note: takeUntil is used in conjunction with take to prevent memory leaks when no value is received until the component is destroyed.

(Without takeUntil, the subscription will persist until the first value is retrieved,

After the component is destroyed, it is impossible to receive the first value, causing a memory leak.)

7. Use appropriate operators

What

Select the appropriate merge operator

SwitchMap: When you want to replace the previous old value with the newly received value

MergeMap: When you want to operate on all received values at the same time

ConcatMap: When you want to take turns with the received values

ExhaustMap: when also before processing a received value, cancel processing value later

Why

A. Using a single operator for the same purpose can help reduce the amount of code you need, rather than using multiple operators chained

B. Improper use of operators can lead to unexpected behavior because different operators achieve different effects

8. Lazy loading

What

Try lazily loading modules in Angular apps if you can.

Lazy loading means loading module content only when it is needed

Why

A. Reduce the volume of applications that need to be loaded

B. The startup performance can be improved by avoiding loading unnecessary modules

Example

[Before]

{ path: 'not-lazy-loaded', component: NotLazyLoadedComponent }

[After]

// app.routing.ts

{ 
  path: 'lazy-load',
  loadChildren:  () => import(lazy-load.module).then(m => m.LazyLoadModule)
}

// lazy-load.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LazyLoadComponent }   from './lazy-load.component';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
         { 
             path: '',
             component: LazyLoadComponent 
         }
    ])
  ],
  declarations: [
    LazyLoadComponent
  ]
})
export class LazyModule {}
Copy the code

9. Avoid nested subscriptions

What

In some cases, data may need to be retrieved from multiple Observables for a specific purpose.

In this case, avoid nesting subscriptions within a subscription block.

A better approach is to use the appropriate chain operator.

For example: withLatestFrom, combineLatest and so on

Why

Code smell/readability/complexity: Not fully using RxJs indicates that the developer is not familiar with the shallow use of RxJs apis

If it is a cold Observable, it will continue subscribing to the first Observable until it completes, and then start work on the second Observable.

If there are network requests, the performance will be a waterfall

Example

[Before]

firstObservable$.pipe(

take(1)

).

subscribe(firstValue => {

secondObservable$.pipe(

take(1)

)

.subscribe(secondValue => {

console.log(`Combined values are: ${firstValue} &

${secondValue}`);

});

});

[After]

firstObservable$.pipe(

withLatestFrom(secondObservable$),

first()

)

.subscribe(([firstValue, secondValue]) => {

console.log(`Combined values are: ${firstValue} & ${secondValue}`);

});

10. Avoid any and define types explicitly

What

When declaring a variable or constant, give it a specific type rather than simply using any

Why

A. When declaring a variable or factory with an unspecified type in TS, its type will be inferred from the given value, which may cause unexpected problems

A classic example is as follows:

Example

[Before]

const x = 1;

const y = 'a';

const z = x + y;

console.log(`Value of z is: ${z}`

/ / output

Value of z is 1a

If the original expected input is y also a numeric type, this can cause unexpected problems.

[After]

These problems can be avoided by specifying an appropriate type for the declared variable:

const x: number = 1;

const y: number = 'a';

const z: number = x + y;

// This input will cause a compilation error to be thrown

Type ‘”a”‘ is not assignable to type ‘number’.

const y:number

By doing this, you can avoid bugs caused by missing types

B. Another benefit of specifying a type is that it makes refactoring simpler and safer

Example

[Before]

public ngOnInit (): void {

let myFlashObject = {

name: 'My cool name',

age: 'My cool age',

loc: 'My cool location'

}

this.processObject(myFlashObject);

}

public processObject(myObject: any): void {

console.log(`Name: ${myObject.name}`);

console.log(`Age: ${myObject.age}`);

console.log(`Location: ${myObject.loc}`);

}

/ / output

Name: My cool name

Age: My cool age

Location: My cool location

Suppose you want to rename the LOC property in myFlashObject to Location

public ngOnInit (): void {

let myFlashObject = {

name: 'My cool name',

age: 'My cool age',

location: 'My cool location'

}

this.processObject(myFlashObject);

}

public processObject(myObject: any): void {

console.log(`Name: ${myObject.name}`);

console.log(`Age: ${myObject.age}`);

console.log(`Location: ${myObject.loc}`);

}

/ / output

Name: My cool name

Age: My cool age

Location: undefined

When myFlashObject is not typed, it appears that the method loC attribute does not exist in myFlashObject rather than the attribute error

[After]

When we add the type definition to myFlashObject, we get a clearer compilation error as follows

type FlashObject = {

name: string,

age: string,

location: string

}

public ngOnInit (): void {

let myFlashObject: FlashObject = {

name: 'My cool name',

age: 'My cool age',

// Compilation error

Type ‘{ name: string; age: string; loc: string; }’ is not

assignable to type ‘FlashObjectType’.

Object literal may only specify known properties, and ‘loc’

does not exist in type ‘FlashObjectType’.

loc: 'My cool location'

}

this.processObject(myFlashObject);

}

public processObject(myObject: FlashObject): void {

console.log(`Name: ${myObject.name}`);

console.log(`Age: ${myObject.age}`)

// Compilation error

Property ‘loc’ does not exist on type ‘FlashObjectType’.

console.log(`Location: ${myObject.loc}`);

}

If you are starting a brand new project, it is recommended that you set strict: true in the tsconfig.json file to enable strict mode and all strict type checking options

11. Use lint rules

What

Lint rules come with multiple preset options such as no-any, no-magic-numbers, no-consle, etc. You can enable specific validation rules in your tslint.json file

Why

Using the Lint rule means that you will get a clearer error when a behavior occurs somewhere that shouldn’t happen

This will improve the consistency and readability of your application code

Some Lint rules even have a specific fix solution for this Lint task

If you want to define your own Lint rules, you can write them

Use TSQuery to write tutorial links for your own Lint rules

A classic example is as follows:

Example

[Before]

public ngOnInit (): void {

console.log('I am a naughty console log message');

console.warn('I am a naughty console warning message');

console.error('I am a naughty console error message');

}

/ / output

Instead of reporting an error, the following message is printed in the console:

I am a naughty console message

I am a naughty console warning message

I am a naughty console error message

[After]

// tslint.json

{

"rules": {

.

"no-console": [

true,

"log", // no console.log allowed

"warn" // no console.warn allowed

]

}

}

/ /.. component.ts

public ngOnInit (): void {

console.log('I am a naughty console log message');

console.warn('I am a naughty console warning message');

console.error('I am a naughty console error message');

}

// Output

Lint reports errors in console.log and log.warn statements. Console. error does not report errors because it is not configured in Lint rules

Calls to ‘console.log’ are not allowed.

Calls to ‘console.warn’ are not allowed.

12. Lean, reusable components

What

Extract reusable snippets of code from a component into a new component

Dumb components as much as possible so they can be reused in as many scenarios as possible

Writing a “dumb” component means that there is no particular logic implicit and that the operation simply depends on the input and output provided to it

As a general rule, the component with the most child node in the component tree will be the most dumb of them all

Why

Reusable components reduce code duplication, which in turn makes them easier to maintain and change

Dumb components are simpler and therefore less likely to be buggy. Dumb components make you think hard about how to extract common component apis and help you identify confounding problems

13. Components only handle presentation logic

What

Avoid encapsulating business logic other than presentation logic into components and ensure that components are only used to process presentation logic

Why

A. Components are designed for control view and display purposes. Any business logic should be encapsulated in its own appropriate method or service, and business logic should be separated from component logic

B. If the business logic is extracted within a service, it is generally better suited to use unit tests and can be reused by other components that require the same business logic

14. Avoid long approaches

What

Long approaches often indicate that they already contain too many tasks and try to use the single responsibility principle

A method should do one thing as a whole, and if there are more than one operation, we can extract these methods into separate functions, making them responsible for their own responsibilities, and then call them

Why

A. Long methods are difficult to read, understand, and maintain. They are prone to bugs because changing one part of the method can affect the rest of the logic within the method. This also makes code refactoring more difficult

B. Methods can be measured in cyclomatic complexity. There are several TSLint methods for detecting cyclomatic complexity that you can use in your projects to avoid bugs and check code availability

15. Dry

What

Dry = Do not Repeat Yourself

To ensure that there is no duplicate code in the repository, extract duplicate code and reference it where it is needed

Why

A. Repeating code in multiple places means that if we want to change the code logic, we need to change it in multiple places, reducing the maintainability of the code

Making changes to the code logic difficult and the testing process lengthy

B. Extracting duplicate code to a place means only one code change and a single test

C. At the same time less code means faster speed

16. Increase cache

What

The response to an API request usually does not change very often, and in such scenarios, you can add a caching mechanism and store the retrieved value

When the same API request is sent again, check whether the value is already in the cache. If yes, the value can be used directly. Otherwise, the request is sent and cached.

If these values change but not frequently, then a cache time can be introduced to decide whether to use the cache or to call again

Why

Having a caching mechanism means that unnecessary API calls can be avoided. By avoiding repeated calls, it helps the application respond faster, no longer having to wait for the network to return, and we don’t have to download the same information repeatedly

17. Avoid logic in templates

What

If you need to add any logic to your HTML, even if it’s just a simple &&, it’s best to extract it into a component

Why

The logic in templates is difficult to unit test and can cause code problems when switching template code

[Before]

// template

<p *ngIf="role==='developer'"> Status: Developer </p>

// component

public ngOnInit (): void {

this.role = 'developer';

}

[After]

<p *ngIf="showDeveloperStatus"> Status: Developer </p>

// component

public ngOnInit (): void {

this.role = 'developer';

this.showDeveloperStatus = true;

}

18. Safely declare a string

What

If you have string variables that only have certain values, it is better to declare them as a set of possible values than to declare them as strings

Why

Bugs can be avoided by providing appropriate declarations for variables: when code is written that exceeds expectations it can be discovered at compile time, rather than at run time

[Before]

private myStringValue: string;

if (itShouldHaveFirstValue) {
   myStringValue = 'First';
} else {
   myStringValue = 'Second'
}
Copy the code

[After]

private myStringValue: 'First' | 'Second'; if (itShouldHaveFirstValue) { myStringValue = 'First'; } else { myStringValue = 'Other' } // This will give the below error Type '"Other"' is not assignable to type '"First" |  "Second"' (property) AppComponent.myValue: "First" | "Second"Copy the code

Join us

We are DevUI team, welcome to come here and build elegant and efficient human-computer design/research and development system with us. Email: [email protected].

The copyright of this article belongs to the original author and is only used for learning and communication. If you need to reprint the translation, please indicate the source information below, thank you! **Best practices for a Clean and Performant Angular Application **