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 **