19

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 { }).

DonkeyBanana
  • 3,266
  • 4
  • 26
  • 65
  • How are you using this component? – yurzui Oct 14 '17 at 09:58
  • Did you read https://angular.io/api/forms/ControlValueAccessor? – jonrsharpe Oct 14 '17 at 10:10
  • `Why is it important to execute the call to onChange(...) and onTouch(...) in writeValue(...)? ` Where did you see it? – yurzui Oct 14 '17 at 10:48
  • @yurzui, here is probably referring to [these](https://github.com/angular/angular/blob/522ec9a25b411cc7596df2348c364867a8bce819/packages/forms/src/directives/default_value_accessor.ts#L60) – Max Koretskyi Oct 14 '17 at 10:52
  • @AngularInDepth.com I think he is referring to this http://take.ms/IGcPJ – yurzui Oct 14 '17 at 10:53
  • @yurzui, yeah, it's the same thing. when `registerOnChanged` is called first time by the formControl the passed callback is stored to `onChange`. Then it's called every time the custom control value is updated "from the inside" – Max Koretskyi Oct 14 '17 at 10:58
  • Yeah, i know it https://i.stack.imgur.com/FLSNx.png. I just wanted to know where he found it – yurzui Oct 14 '17 at 10:59
  • @jonrsharpe Did you suggest a link to a site that I listed as my source of information for the implementation? Or am I missing your point, perhaps? That's the precise link I'm referring to in my questions as of where I've obtained the names of the methods to be implemented... – DonkeyBanana Oct 14 '17 at 19:44
  • @yurzui I haven't seen the actual text. I've seen those calls to be made in every example I've found. It made me conclude that there's something wise in doing that and that I'm simply too ignorant about to see it. Was I mistaken? (As for how I'm using the component - as a adapted input box with some features I'd like to have encapsulated are pre-implemented.) – DonkeyBanana Oct 14 '17 at 19:47

2 Answers2

24

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).

Mikkel R. Lund
  • 2,336
  • 1
  • 31
  • 44
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Great answer. Two follow-ups, though. #1 If I have registered `onChange()` but don't invoke it from `writeValue()` (only setting the value to the backing field but not propagating it to `onChange()`) I still get the value to the form group in the parent component. So what's **not** working now? I can't see it. Do you have a clear example on what's not working out there? #2 I've seen `setDisableState()` without question mark, with question mark after the method name and with question mark after the parentheses. Is that the same meaning? What's that all about? – DonkeyBanana Oct 14 '17 at 19:57
  • @DonkeyBanana, please read the article I referenced and if you still questions after that create a stackblitz demo and let me know what's unclear from the article – Max Koretskyi Oct 18 '17 at 08:33
  • 6
    You have to call `onChange` if the user interacts with your DOM control and changed its value, so that the value is written to the form-value. Calling it in `writeValue` is wrong. The form will call this function, if the programm changes the value of the form-control. You only have to update your DOM control to reflect the current form-value. – Lars Mar 30 '18 at 14:25
  • And `onTouch` should be called, wenn the user "touches" your DOM control, so the state of the form-control changes to "touched". It is good practice to show validation errors of a form-control, after the user has touched the control, so you don't have lots of "this field is required" etc. messages if you show a fresh form. – Lars Mar 30 '18 at 14:25
  • @Lars, was your comment related to my answer or question of DonkeyBanana? – Max Koretskyi Mar 30 '18 at 14:47
  • @AngularInDepth.com it's just an addition to your answer and the question to make clear, what these functions are and when they have to be called. The mechanism behind the ControlValueAccessor is straight forward, if you think about it. – Lars Apr 01 '18 at 06:27
  • @Lars, what is `onChange`? – Max Koretskyi Apr 01 '18 at 07:32
  • The function that is registered with `registerOnChange`. – Lars Apr 01 '18 at 15:37
  • @Lars, okay, so using these terms one should call `onChange` when a value is changed in a control in the form (input etc.) and `writeValue` when a value is changed as a result of interaction with FormControl inside a component. That's what my answer is about. What's your addition? – Max Koretskyi Apr 01 '18 at 15:44
  • 1
    I missed a clear statement, that calling `onChange` inside `writeValue` is wrong, because `onChange` has to be used to inform the FormControl about a change of the value and `writeValue` is used to update the UI. Combining these function is kind of a circular dependency. Everything is ok with your answer. I learned about the ControlValueAccessor just a few days ago and stumbled upon the implementation DonkeyBanana quoted. So I felt the need to comment it. – Lars Apr 02 '18 at 16:26
  • 1
    You have saved me from excruciating pain that I was suffering from for last 3 days. Article provides in depth explanation. Thanks!! – Manish Mar 10 '20 at 19:48
  • This answer misses the point of the question. Since the code in the article is correct, it seems to me that you misunderstood what the OP was asking. @Lars correctly pointed this out, and the other answer (https://stackoverflow.com/a/60758101/3744574) is a better one. – Avius Mar 17 '21 at 18:38
13

I don't think the accepted answer answers the most central question in a succinct way:

Why is it important to execute the call to onChange(...) and onTouch(...) in writeValue(...)?

It isn't. You do not need to call onChange in writeValue. It is not the intended usage (see documentation link below).

What will go wrong and under what circumstances can it be expected?

Expect nothing to go wrong. Elaborate answer:

If you were to call onChange from inside writeValue you'll notice:

  • First onChange call does nothing. That's because onChange callback hasn't been registered (ie registerOnChange not called) when writeValue is first called.
  • Later calls to onChange from inside writeValue updates your model (with emitModelToViewChange = false to avoid recursively calling writeValue) with the value you gave it - which you just received - from your model.

In other words, unless you rely on your component to somehow immediately change the value it receives in writeValue, and for it to pass those changes back to your model but only on second and later invocations of writeValue, you're safe to not call onChange from writeValue.

See example in documentation here: https://angular.io/api/forms/ControlValueAccessor

corolla
  • 5,386
  • 1
  • 23
  • 21
  • Interesting fact — none of the example links provided by the OP actually has `this.onChange()` inside `writeValue()` now. – enkryptor May 24 '21 at 11:42
  • interesting indeed - could it really be the internet course-corrected? – corolla May 26 '21 at 17:48
  • 1
    @corolla I want to transform the value I pass to my formControl according to different criteria. So I need to call the onChange() method in WriteValue(). How can I do it then since registerOnChange() is triggered after writeValue() ? – PepeW Aug 10 '21 at 22:46