Why do I need call onChange and onTouch in writeValue when implementing ControlValueAccessor in Angular?

DonkeyBanana picture DonkeyBanana · Oct 14, 2017 · Viewed 8.5k times · Source

I have implemented the following component. It works and behaves as expected. Nevertheless, as the implementation of ControlValueAccessor was new to me, I had to follow a blog without understanding the deeper details of a few sections. So this is a type of "it works but why?!" situation.

@Component({ selector: ..., templateUrl: ..., styleUrls: ...,
  providers: [{ provide: NG_VALUE_ACCESSOR, 
                useExisting: forwardRef(() => InputTextComponent), 
                multi: true }]
})
export class InputComponent implements ControlValueAccessor {

  constructor() { }
  @Input() info: string;
  onChange: any = () => { }
  onTouch: any = () => { }

  writeValue(input: any): void {
    this.info.value = input;
    // this.onChange(input);
    // this.onTouch();
  }

  registerOnChange(_: any): void { this.onChange = _; }
  registerOnTouched(_: any): void { this.onTouch = _; }

  setDisabledState?(state: boolean): void { }

  onEdit(value: string): void { this.onChange(value); }
}

When I've got it working, I commented out the second and third line of writeValue(...) method and, as far I can tell, nothing broke. Those calls are consistently suggested by other blogs as well, so I'm convinced that it's improper to omit them. However, I don't believe in magic and prefer to have a concrete reason for doing things.

Why is it important to execute the call to onChange(...) and onTouch(...) in writeValue(...)? What will go wrong and under what circumstances can it be expected?

As a side quest, I also tried to comment out the other methods and discovered that I couldn't tell anything going bananas when I removed setDisabledState(...). When can that one be expected to cause problems? Does it really need to be implemented (I've seen version with question mark both before and after the parentheses with parameters like so: setDisabledState?(state: boolean): void { } but also like this: setDisabledState(state: boolean)?: void { }).

Answer

Max Koretskyi picture Max Koretskyi · Oct 14, 2017

Read this article that explains the ControlValueAccessor in great details:

You usually need to implement ControlValueAcceessor interface on a component if it's supposed to be used as part of an Angular form.

I commented out the second and third line of writeValue(...) method and, as far I can tell, nothing broke.

This is probably because you're not applying any form directive - formControl or ngModel that links a FormControl to your custom input component. The FormControl uses writeValue method of the InputComponent for communication.

Here is the picture from the article I referenced above:

enter image description here

The writeValue method is used by formControl to set value to the native form control. The registerOnChange method is used by formControl to register a callback that is expected to be triggered every time the native form control is updated. The registerOnTouched method is used to indicate that a user interacted with a control.

Why is it important to execute the call to onChange(...) and onTouch(...) in writeValue(...)? What will go wrong and under what circumstances can it be expected?

This is the mechanism by which you custom control that implements ControlValueAcceessor notifies the Angular's FormControl that a value in the input has changed or the user interacted with the control.

...discovered that I couldn't tell anything going bananas when I removed setDisabledState(...)...Does it really need to be implemented?

As specified in the interface this function is called by the forms API when the control status changes to or from "DISABLED". Depending on the value, it should enable or disable the appropriate DOM element. You need to implement it if you want to be notified whenever the status of an associated FormControl becomes disabled and then you can perform some custom logic (for example, disable your input component).