Many times in Angular you reveal some component using the *ngIf directive, like so:

<user-card *ngIf="selectedUser !== null" [user]="selectedUser">

</user-card>

It works fine and if you set the selectedUser property in the parent component, the user-card component is shown. Then if you clear the selectedUser property and set it back to null the component gets hidden.

But what if you just switch from one value to another?

When you switch from one user to another without setting the selectedUser property to null in the meantime, the user-card component will simply not re-render as it’s already there. Only the input will change. The *ngIf will not have any reason to remove the user-card component from the DOM. This means the ngOnInit of user-card (if any is set) will not be re-run.

Let’s suppose we have some init logic inside of the user-card component (as it’s quite common scenario for a component). This could be something like this, or something way more complex:

@Input() user: User; ngOnInit() {

const { id } = this.user;

this.http.get(`api/users/${id}`)

.subscribe(userData => this.userData = userData);

}

In such situation, it would be good to somehow make sure, the init logic of the ngOnInit hook is run whenever the user is changed and not only when the user changes from null to something. Otherwise it would mean we have the second user already selected, however the user-card component is still operating based on the init logic run for the first user provided.

Why not to simply use a setter?

Most of the Angular developers would instantly say: just use a setter if you want to have some logic triggered every time the input changes.

Just replace this:

Input() user: User;

with this:

@Input() set user(value: User) {

this._user = user;

this.init();

} get user() {

return this._user;

} private _user: User;

I’d bet you already noticed, how the complexity of the component code increases. Every single-line @Input would need to be transformed into a 10-line code block. 1000% more code per each input. This is insane :)

And it’s not the end of the refactoring story, as normally the init logic depends on multiple inputs, and not just one.

Would you then repeat the init every time the input is set? And if there are 10 inputs, would you call the api 10 times on init? Or would you write a complex if clause just to make sure you call the api only when needed, and only when all the inputs are known. Or maybe you would replace a simple ngOnInit method with some complex chains of Subjects and Observables just to make it clean. Or maybe you would use ngOnChanges instead, adding some if clause that assures nothing is broken when OnChanges is run before OnInit.

No matter what, it always sounds like a complex refactoring. Especially if you’re dealing with some “mature” and already “overgrown” component.

And what if we’re going too far?

Maybe we see these challenges and additional complexity because we’ve started fixing the problem from the wrong end and with the wrong tools?

OnChanges, Input setters and other stuff are good. They help you not destroy your component unnecessarily, they help you avoid costly DOM manipulation when the component builds a huge part of the UI, they save you from some unwanted screen flickering when you remove and add new component, etc.. But sometimes they are simply not needed and they just burden the component code.

Let’s take a step back and think what is the real problem here. In this case we’re not necessarily interested in handling input data changes in a single component instance. We’re more interested in generating a new component instance whenever the selected user changes in the parent component. The problem we have is caused by the fact we are using the *ngIf directive and the fact this directive does nothing when its input changes from one truthy value to another.

Is there any other directive that re-renders its template whenever its input changes? In fact there is.

And what if I told you: “There’s another way”?

We could simply use the *ngFor directive! As we do for collections.

Please notice: whenever you render a collection of components using the ngFor directive you don’t have to worry about the issue described above, as in the ngFor whenever a new item is added to the collection then a new component gets initialized. And this is exactly what we want, but in our case we are dealing with a single-item collection (we have just one selected user at a time).

So let’s just write something like this in the parent component template:

<ng-container *ngIf="selectedUser !== null">

<user-card *ngFor="let user of [selectedUser]" [user]="user">

</user-card>

</ng-container>

And… DONE. Believe me or not, but it works. It works like a charm!

And this is what we do:

We make sure we have some user selected - we use the ngIf directive to confirm that. We create a collection with just one item and we pass it to the ngFor directive to render user-card component.

Please notice a dynamic array initialization, which does the trick:

*ngFor="let user of [selectedUser]"

We create an array by simply writing this directly in the template:

[selectedUser]

What’s so special about that? Well, you don’t have to change anything in the user-card component, nothing at all, not a single line! And still, from now on, every time you change the selectedUser property, the user-card component will be re-rendered and its ngOnInit logic will be run based on the new input.

And we could go even further with simplifying the code. We could actually merge the ngIf check that verifies if any user is selected with the ngFor. If we pass an empty array to the ngFor nothing will be rendered of course.

<user-card

*ngFor="let user of (selectedUser ? [selectedUser] : [])"

[user]="user">

</user-card>

Now just take a look how nice & simple it looks in the VS Code: