Angular doesn't provide any syntactic sugar to control the binding frequency of inputs.
However, you can control when to react when inputs are updated. There are two ways, and either or both could be used to get the desired behaviour:
- Using a setter for Input and conditionally forward the update
to the component body or component's template.
- Switching the change detection in the
component on or off using
ChangeDetectorRef
when inputs are
satisfied.
Note that turning the change detection off doesn't mean that the inputs won't be updated. Inputs will always update, and so ngOnChanges
will be called whether or not change detection is off or on. However, the template won't update against updated inputs if change detection is off. See the codesandbox to understand this effect.
Example 1
To explain the point 1 above, consider the following:
_input1: number;
@Input() set input1(input: number) {
if (this._input1 === undefined || this._input1 === null) {
this._input1 = input;
}
}
Consuming in template
<div>Bound Input1: {{ _input1 }}</div>
_input1
is component's local copy and is only updated when desired i.e. in the above particular case, the local copy in only updated if it's previously null or undefined. input1
is the input property. Here we assume that an undefined or null value will never be considered a valid value and unless input1
is passed a non-null non-undefined value, we will consider that the "bind once" hans't occurred.
Example 2
ChangeDetectorRef
excerpt from docs:
Base class that provides change detection functionality. A change-detection tree collects all views that are to be checked for changes. Use the methods to add and remove views from the tree, initiate change-detection ...
So ChangeDetectorRef's method detach
could be used to detach a component from change detection tree. To demonstrate point 2 above, the following example waits until all inputs are satisfied and then turns off the change detection:
import {
ChangeDetectorRef,
Component,
Input,
OnChanges,
SimpleChanges
} from "@angular/core";
@Component({
selector: "bind-until-all",
template: `
<div>Input1: {{ input1 }}</div>
<div>Input2: {{ input2 }}</div>
<div>Input3: {{ input3 }}</div>
`
})
export class BindUntillAllComponent implements OnChanges {
/**
* We assume that a null or undefined value is not an empty.
* And until a non-empty value is passed to an input, it'll be considered as not-assigned.
*
* Based on the use case, define what is a suitable "empty" value
* and assign that value as the default value to the inputs.
*/
@Input() input1: number;
@Input() input2: number;
@Input() input3: number;
private isDetached: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
ngOnChanges(changes: SimpleChanges) {
// Check whether all inputs are satisfied i.e. they have non-empty values assigned
// For our case, type === "number" satisfies that the value is non-empty
const areAllInputsSatisfied = [this.input1, this.input2, this.input3].every(
(n) => typeof n === "number"
);
// Stop change detection after triggering the manual change detection once
if (areAllInputsSatisfied && !this.isDetached) {
this.cdr.detectChanges();
this.cdr.detach();
this.isDetached = true;
}
}
}
Live example in the above mentioned codesandbox.