Angular — Four old fashioned practices that you should NOT carry forward to Angular

Before the invention of modern JS frameworks, most websites were built with static HTML plus classic JS libraries, such as jQuery. Many practices have been formulated based on this stack, but some of them may no longer applicable with modern frameworks nowadays. Here I have summarized four classic web development practices that are not suitable anymore for Angular.

1. Manipulating instances

Element selector plays an important role in classic web development. When we want to perform action on an element, we use the selector to get the corresponding instance and manipulate the instance directly. Selecting and manipulating instances is a common and the preferred way of doing things in classic websites. Yet, direct instance manipulation (with features like @ViewChild), such as calling a public component method, reading some component attributes, etc., is a less preferred approach in Angular. The reason is we cannot ensure the reactivity when doing such manipulations. For example:

@Component({
selector: 'custom-button',
...
})
export class CustomButtonComponent {
activated = false;
public setActivated(value: boolean) {
this.activated = value;
}
}

We need to run setActivated manually every time we know when there is change in the state:

@Component(...)
export class TestPageComponent {
activated = false;
@ViewChild('customButton1') customButton1: CustomButtonComponent;
@ViewChild('customButton2') customButton2: CustomButtonComponent;
onDataLoad() {
this.activated = !!this.data.length;
this.customButton1.setActivated(this.activated);
this.customButton2.setActivated(this.activated);
}

...
}

We are managing the reactivity ourselves here. It is risky as it means a defect if new codes break the assignment chain, which is very common. States are not automatically reacted to change, which is the major problem. To avoid this issue, we can simply utilize the official @Input binding and @Output events.

@Component({
selector: 'custom-button',
...
})
export class CustomButtonComponent {
@Input activated = false;
}

Now we can ensure activated inside the button is always stick to the parent state.

@Component({
...
template: `
<custom-button [activated]="activated">Button 1</custom-button>
<custom-button [activated]="activated">Button 2</custom-button>
`
})
export class TestPageComponent {
activated = false;

onDataLoad() {
this.activated = !!this.data.length;
}

...
}

There is also an advantage of having less code potentially and so, less complexity. Although direct manipulation is indeed not avoidable for some use cases, such as third party library integration, we should always try to avoid it when we design our own components.

2. Modifying HTML

Another common operation we may do in classic websites is modifying HTML with JS in runtime, such as changing inner HTML, updating classes, etc. Traditional web pages use static HTML, so being able to modify HTML dynamically with JS is essential for some advanced UX designs. Yet, modern frontend frameworks no longer use static plain HTML. The solution Angular used is HTML template. In simple, we may consider it as native HTML with a set of additional syntax and logic being added to improve the functionalities. Modifying HTML manually with JS is not recommended in Angular as it can be quite dangerous. The Angular template is involved in many different parts of the Angular core engine. Modifying HTML directly can lead to unexpected side effect if it is not used with caution. The official approach of handling HTML change is to work with the Angular change detection mechanism with Angular HTML template syntax.

<label>{{ isModification ? 'Modify' : 'Create' }}</label>

After years of development, the existing version of Angular template already covers most of the use cases where we need to modify HTML. What is more, it is more declarative that all cases of layout are explicitly coded in the template. It is much easier for other developers to follow the logic as they can figure out what may happen by simply looking into the templates. There are some rare UX designs that we may still need to modify the HTML directly, such as positioning a modal or dropdown component. Yet, we should always try to utilize the Angular template whenever possible for the sake of safeness and maintainability.

3. Relying on callbacks

Callback is an important feature for asynchronous programming. It is highly utilized in traditional web pages, such as sending HTTP calls. First of all, I would like to clarify that using callbacks in Angular is totally fine, but there are some cases that RxJS (RxJS is a library that is officially supported by Angular for composing asynchronous and event-based programs by using observable sequences.) can do the job better, which is mainly about cross component or service communication. The reason is again, reactivity. Callbacks across different component or service always lead to non-reactive codes. For example, assume we use callback to implement an API call, we are going to have something like this:

@Component(...)
export class TestPageComponent implements OnInit {
data: string[];
ngOnInit() {
this.dataService.getData(result => {
this.data = result;
});
}
...
}

We always need to have extra logic to handle the assignment of data from the callback results to states. It can be much cleaner if we utilize RxJS:

@Component(...)
export class TestPageComponent implements OnInit {
data$: Observable<string[]> = this.dataService.getData();
...
}

We can then just use the async pipe to access the data:

<div>{{ data$ | async }}</div>

Another advantage of using RxJS over callback is scalability. RxJS supports many use cases that can be quite tricky to achieve with callback, such as having multiple users on the same source:

@Component(...)
export class TestPageAComponent {
data$ = this.dataService.getData();
...
}
@Component(...)
export class TestPageBComponent {
data$ = this.dataService.getData();
...
}

There are many other features of RxJS that work very well to cater some classic Angular problems. To master Angular, RxJS is something that we definitely cannot miss.

4. Using configuration objects

Wrapping multiple arguments into an object for configuration is a common practice in classic JavaScript. Yet, it is an anti-pattern for Angular component.

export interface Configuration {
enableFilter: boolean;
enableSort: boolean;
...
}@Component({
selector: 'custom-grid',
...
})
export class CustomGridComponent {
@Input() configuration: Configuration;
...
}

There are two problems doing this in Angular. First, it hinders the setting of default value. We simply need more codes to do that, which also means extra complexity. Here I have written another article previously for a more detailed explanation to this issue:

The another problem is that object binding can make the implementation of setter method very tricky.

@Input set configuration(configuration: Configuration) {
...
}

Bound setter method traces only the reference. After the object is bound, changing its attributes won’t trigger the setter method again. It means we will need extra codes to handle the data mutation, which can be very complicated. We can avoid this issue by destructuring it and keep the value primitive:

@Input set enableFilter(enabled: boolean) { ... }
@Input set enableSort(enabled: boolean) { ... }

This will not only save development time, but also keep the code complexity low for better long term maintainability.

Thanks for reading! Hope you find this article helpful. Any comments would be highly appreciated. :D

Web developer from Hong Kong. Most interested in Angular and Vue. Currently working on a Nuxt.js + NestJS project.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store