Why Angular Input Setter is Only Being Fired Once
Examine various approaches to pass data between components
Working on an Angular App requires composing different components and passing data between them. A common approach to passing data from the parent to child components is using the input property setter. We all know getter and setter. They are simply for intercepting access to the property.
Surprisingly, there is a subtle difference in Angular component input setter behavior compared with the “standard” TypeScript setter. I found it when working on the following issue.
Original problem
To illustrate the issue, I set up a contrived example as below.
1 | // Parent Component |
In the above example, we have two components: parent and child. data
is a simple input setter in the child component. When a user clicks on the “set to Parent
” button from the parent component, a string value will be set to “Parent,” and the child component setter will be invoked.
Actually, the above statement is only true for the first time. If you click on the “set to Parent
” button multiple times, the child component setter won’t be triggered from 2nd time onward.
Thus, it looks like the input setter is only fired once if the value of the setter hasn’t been changed.
Root cause
I dig inside the angular source code to understand the root cause of this behavior. In the following [bindingUpdated](https://github.com/angular/angular/blob/d1ea1f4c7f3358b730b0d94e65b00bc28cae279c/packages/core/src/render3/bindings.ts#L52)
function line 7, Angular checks value change by comparing the new value with the old value in its component’s logical view. It only updates binding when the value actually changes.
The Angular Input setter is designed to ignore the binding update if the value hasn’t changed. In contrast, the setter in a normal TypeScript class will be triggered upon every invocation regardless of whether the value changed.
For this use case, I want to trigger the reset of the child component whenever the “set to Parent
” button in the parent is clicked. How can I achieve this?
Workaround
The first workaround I tried was to use ngOnChanges
. I added the following code into the child component and hope it will capture the data when the “set to Parent” button is clicked.
1 | ngOnChanges(changes: SimpleChanges) { |
But the console logs only print the first time the button clicks. Obviously, the underlined binding code for ngOnChanges
is the same as the Input setter.
The second attempt is to expose a new public method setParent
in the child component. From the parent component, we create a reference to the child component with ViewChild
decorator and invoke the setParent
method upon the button clicking.
1 | // Child Component |
This approach works, but I am not very satisfied with it. Exposing a method and directly calling another component is not a clean way. It creates a tight coupling between the two components, which will get worse when the method is consumed by more than one parent component.
I still prefer to use the input property, as it creates a contract between components. Can we still use the input property setter by working around the restriction?
A better approach using observable
If we go back to the Angular source code, it uses [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
to detect value changes.
1 | If(Object.is(oldValue, value)) |
When comparing two objects, Object.is
will return true if “both the same object (meaning both values reference the same object in memory)
”.
Thus, if we pass into the input property with an observable object that contains the same value, Angular will take it as a changed value!
Below is our new implementation using observable as an Input property.
1 | // Parent Component |
In the above implementation, we use an Input setter as the Subject
type and subscribe to the data change in the child component. The child component subscription captures every click of the “set to Parent” button.
You can try out the above example at the stackblitz project.
Input property setter vs. ngOnChanges
In the previous sections, we tried the Input property setter and ngOnChanges
life cycle hook.
They are two ways to detect and react to data changes, and can be used to pass parameters between components. Both will only be triggered if the binding value changes.
They can be used in different use cases. With ngOnChanges
you have access to all property changes in one place. For Input property setters, it only applies to a single property.
There are other common ways to share data between components
- via shared service with BehaviorSubject
- via the
@Output
decorator andEventEmitter
from a child or sibling component
Summary
Although the property setter in TypeScript is simple and straightforward, the devil is in the details: there is a subtle difference for the Angular input property setter on change detection. This article explores resolving the issue of sending repeated values between two Angular components.
There are different ways we can choose to pass data between components. Using @Input
and @Output
is my preferred method, as they serve as a contract between components and make the components more reusable.
If you like this article, you may also like to read my other Angular article below.
Angular State Management with Observable Service Pattern
Angular state management is the core of any Angular App, but there is no one-size-fit-all solution.
javascript.plainenglish.io
I hope you find this article useful. You can find the sample code in this stackbliz project.
- Title: Why Angular Input Setter is Only Being Fired Once
- Author: Sunny Sun
- Created at : 2022-01-27 00:00:00
- Updated at : 2024-08-16 19:46:32
- Link: http://coffeethinkcode.com/2022/01/27/why-angular-input-setter-is-only-being-fired-once/
- License: This work is licensed under CC BY-NC-SA 4.0.