Picture of stairs coming out of the see.
Photo by Francesco Ungaro from Pexels

A deep dive into Angular Inputs

At the beginning of the creation of the Angular framework, the angular team made a great choice by going with the component pattern. Based on what is going on in the JavaScript world this a direction all the big players are taking (Vue, React, Svelte). The component seems to perfectly fit the nature of HTML itself. This global movement caused components to be the focal point of the angular framework and that is why they are one of the most complex features of it. You can easily say they are the backbone of every application. In this article, I would like to take a closer look at the components mentioned and focus on the methods of communication between them.

Methods of communication

There are a couple of ways of communicating between components in the angular framework. Either you can do it with: services, template variables, referencing with queries like @ViewChild() or @ContentChild(), you can even inject a parent component into a child one using the dependency injection mechanism. But the one method which I haven't mention and which is also the most commonly used one is @Input().

@Component({
	selector: 'basic',
	template: `...`
})
class Component {
	
	@Input()
	value: string;
	
}

It's a very easy technique to use, all you need to do is mark property with the @Input() decorator and that is it. It's ready to use. The simplicity of this mechanism is astonishing. The component class properties are bound with another outside value via the brackets mechanism used on the HTML selector tag.

<basic [value]="'Awesome value !'"></basic>

The dual nature of the component

The component is an interesting part of the angular, because of its dual nature. It represents two worlds at the same time. From one point of view, a component is a class, so it has all of the benefits that come from the object-oriented programming world. From another point of view, it has a template or you can maybe even say it is a template (in order to create a component you have to declare it in the HTML). By combining these two worlds it gives us lots of benefits, one of them is property bounding between the class and the HTML template.

The dual nature of the component
The dual nature of the component

Input

Inputs, just like components, also have a dual nature because they are used to change component properties and also to set the initial value of properties. These actions may sound similar but when it comes to complex enterprise-scale applications with a complex bootstrap process, they may be used in completely different ways. If you think more about it inputs kind of brake the single responsibility principle as their duality represents their schizophrenic nature, but I will not go deeper because it's a topic for a separate article.

The single responsibility principle
The single responsibility principle

Bootstrap process

We discussed before that by declaring a component selector in the HTML and using inputs you can bound value with the component class property. But how does it actually work, when the class part of the components gets the value declared in the template part? When the angular bootstraps the whole application, in one of the early stages it builds a hierarchy of views that represent a components tree (angular uses the view as an inner representation of components, an object which combines two natures). In order to do this, the framework needs to create an object representation of each component, so angular instantiates them by invoking constructors. Before the framework starts to bound data from the template to the class part it creates the whole application tree which represents the components hierarchy. Angular uses the lifecycle hooks mechanism to give more details to us developers about what is happening with the component and allows us to run code on these special moments. After the view hierarchy is created, angular takes values from the component inputs and bounds it with views. The framework notifies us of this process by allowing us to react to it with the ngOnChanges hook. It is important to notice that ngOnChanges happens before the ngOnInit hook. So when the ngOnInit hook occurs, the component has obviously been created and it has all the values needed to work.

Input bootstrap
Input bootstrap

Change value

Now we understand how component sets its initial value, but what about changing it when the application is running? Changes made by the user are always represented by browser events. Whether they are mouse click events, HTTP calls, or timers like the setTimeout function, they represent widely recognized events. Very simply stated, angular catches these events and reacts to them in a different way. One of them is changing the input value, which means the changes in the input properties are recognized by the framework. Every input change is also broadcast with the ngOnChanges lifecycle hook.

Event change
Event change

So far we described the process of bounding values with the component class properties, now let's take a look at how a component can react to changes made to a specific input property.

Hook ngOnChanges

In the previous part of this article, I wrote that angular uses a hook method for notifying changes. Angular hooks represent an implementation of the dependency inversion principle by allowing us to react to the framework's inner mechanics. The lifecycle hook ngOnChanges takes as an argument object of the type SimpleChangeshttps://angular .io/api/core/SimpleChanges:

export interface SimpleChanges {
	[propName: string]: SimpleChange;
 }

export class SimpleChange {
	previousValue: any;
	currentValue: any;
	firstChange: boolean;
	isFirstChange(): boolean;
}

The ngOnChanges hook is invoked immediately after the components class properties are supplied with values, so when the component is ready to be used. The argument changes has the currentValue and the previousValue which allows us to distinguish between these two different operations. It helps to deal with what I wrote earlier about the duality of this method.

@Component({...})
class Component {

	@Input()
	value: string;

	ngOnChanges(changes: SimpleChanges) {
		if (changes.value) {
		// do something
		}
	 }

}

Setters

Another commonly used technique which allows us to do something when one input changes are setters. Instead of declaring an input as a property, with the JavaScript magic, we can declare it as an accessor. This allows us to run some code when angular will try to change input property:

@Component({...})
class UserComponent {

	@Input()
	set user(users: User) {
		this._user = user;
	}

	private _user: User;

}

This is a very clever method. It allows us to run some code when the user's property changes. Let's take a look at a more complex, more real-life example:

@Component({...})
class Component {

	@Input()
	url: Url;

	@Input()
	set users(users: Array<User>) {
		this._users = users.map((user) => {
			return user.avatar = this.url.baseUrl + user.avatar;
		});
	}

	private _users: Array<User>;

}

We can see two input setter methods url and users where the second one depends on the url property which is used to set a proper link to the avatar.

Now let's see what will happen when someone, during the process of code refactoring, changes the order of the declared inputs.

BaseUrl error.
BaseUrl error.

You can see that the application throws an error. This is caused by the fact that the input values are assigned in the order of declaration in the component! So by changing the order of the inputs, someone has created a bug. Trust me this issue would not be easy to find in an enterprise-scale application. Let's see how the code is interpreted, angular firstly try to assign value to the _users property, but cannot do so because _url is undefined. The fact that inputs are assigned by the order of declaration should be taken into consideration.

Input setters may run various code, so in a way, the break the single responsibility principle because that code may generate side effects, like in the described example.

Before we try to find a solution let's take a look at a basic example:

@Component({...})
class Component {

	@Input()
	set first(value: string) {
		console.log(1)
	}

	@Input()
	set second(value: string) {
		console.log(2)
	}

	ngOnChanges() {
		console.log(3)
	}

}
Logs from the console
Logs from the console

Angular firstly invokes the accessor's method in the order of declaration, so it logs value 1 and 2. Then we can see that ngOnChanges is invoked immediately after the setters. It is invoked only once, not each time for each setter (which is a common misunderstanding). The ngOnChanges method allows us to do something when all the inputs have been assigned with proper values. It is important to understand that when the ngOnChanges method is invoked all the inputs have values and they are ready to use. Link to the example on the stackblitz https://stackblitz.com/edit/angular-g1nvau

The easy to find solution to the user-list example is to move logic from the input accessor to the ngOnChanges hook:

@Component({...})
class Component {

	@Input()
	url: string

	@Input()
	users: Array<User>;

	ngOnChanges() {
		this._url = 'https://' + url;
		this._users.map((user) => user.avatar = _this.url + user.avatar)
	}

	private _url: string;
	private _users: Array<User>;

}

Now you can see that code responsible for dealing with the dependency between two inputs is in one place. It is clearer and more readable because it is not hidden between the two methods. It is easy to imagine a more complex example when there are more than one input and the dependencies between them are like a spider's web.

In my opinion, setters bring only a potential confusion to the code. There is also no performance-wise aspect to using them because the hook ngOnChanges is invoked immediately after setters. During working week hours I act as a Frontend Developer in a large enterprise-scale application. In order to deal with issues that may come from using setters with inputs, we agreed on a convention in which we will always favor ngOnChanges hook over accessor methods. After a while, we simply stopped using setters and we basically don't have issues with this kind of problem.

Naming convention

So far we focused on the components, but what about directives? I guess you all know that components derive from directives, so in a way, they both share common characteristics. You may think of a component as a superset of a directive functionalities. The main difference between them is that directives have no templates, so basically they are components without the view. One of the common features between them is also the main point of this article and it is the @Input().

Both components, as well as directives, are created by using their selector name in the application template. It is possible to assign both of them to the same DOM element. This situation may cause a clash of names for the inputs like we can see here:

<component [value]="'Value for the component'"
		   [tooltip]="true"
		   [value]="'Tooltip text..'">
</component>

How to distinguish which [value] input relates to the component and which one to the tooltip directive?

To fix this issue many teams uses the convention which says: The directive inputs should be prefixed with the name of the directive as well as they should be close to each other:

<component [value]="'Value for the component'"
		   [tooltip]="true"
		   [tooltipValue]="'Tooltip text..'"
		   [tooltipDirection]="'Down'">
</component>

This solution looks clean when you look at the template where it is used, but not so much when you look at the class part of the directive. Now every input property needs to have a prefix. This naming may lead to a messy code where class variables are like bankAccountTooltipValue instead of value. The great Uncle Bob wrote:

This means that we should keep our local class variables short because their names are used in the specific context which that class brings. On the other hand, the input name, used in the template, should be more descriptive because it is used in the broader application scope. Angular offers a solution for this issue, we can simply rename the input by using @Input() decorator with the new property name:

@Input("tooltipValue")
value: string;

Now you can have both, clean class part and more descriptive names in the template.

Input name prefixing

When you have many inputs in a directive it may be tempting to avoid the code duplication by creating a local variable with the directive name.

const tooltip = `tooltip`;

@Directive({
	selector: `${tooltip}`
})
class TooltipDirective {

	@Input(`${tooltip}Value`)
	value: string

	@Input(`${tooltip}Direction`)
	direction: string;

}

Unfortunately, this nifty little trick doesn't work with IDE, which means that it will not recognize the tooltip directive when you will try to use it in the template. It's better to avoid this practice if you want your IDE IntelliSense to work.

Required Input

Inputs are optional by default. What I mean by that is you don't have to use them in order for a component to work. It is not a technical error to use a user list component without providing users to it. But what is the purpose of putting such a component in the application in the first place if we don't provide it with proper data? Once again angular comes with a clever mechanism for dealing with this issue. Let's take a look at the method of how to force a component to be always used with particular inputs:

@Component({
	selector: 'user-list[users]',
	...
})
class UserListComponent {

	@Input()
	users: Array<User>;

}

In the template:

<user-list [users]="myUsers"></user-list>

As you can see we have modified the component selector by adding to it the input name in the brackets. This way angular will force us to always use it with the required input and if we don't it will throw an error at compilation. Basically, IDE will not allow us to use this component without specifying the required input.

Summary

Inputs represent the most commonly used method of communication between components in the angular framework. That is why it's good to know all of their benefits and the techniques which may help write better, more complex, and less buggy code.

Luke, Software engineer, passionate about best practices, frameworks React, Vue, patterns and architectures for modern web
				development, open-source creator, TypeScript enthusiast, Generic UI Technical Blog

Luke

Software engineer, passionate about best practices, frameworks React & Vue, patterns and architectures for modern web development, open-source creator, TypeScript enthusiast.