Never again be confused when implementing ControlValueAccessor in Angular forms

Thanks a lot for this post!

I was following the tutorial Creating a custom form field control and was confused about the role of the @Input value. The setter is only used internally in writeValue and the getter is not used at all. Instead, onChange is used (initialized in registerOnChange).

I was actually expecting to be able to pass an instance of a class to my implementation of ControlValueAccessor via the value setter and also get an instance of the same class back via the value getter (in both cases, additional checks can be performed). Instead, I get a mere object when reading from my implementation of ControlValueAccessor. Did I miss something or is this the way it is supposed to work?

1 Like

Hey, thanks for asking the question, could you maybe put together a simple demo of what you’ve done on ng-run? It’s easier to help when we can see the existing setup.

I think you can just have a look their stackblitz example.

They set up a setter and getter for @Input value but the getter is not used at all (at least to my understanding; the setter is used in writeValue):


// registration of the cb
registerOnChange(fn: any): void {
    this.onChange = fn;
  }

// whenever the input changes in HTML, the value gets updated
_handleInput(): void {
    this.onChange(this.parts.value);
  }

So what happens is that you deal with a mere object (not an actual instance of MyTel):

this.parts = formBuilder.group({
      area: '',
      exchange: '',
      subscriber: '',
    });

But instead I would like to deal with an instance of MyTel. Maybe that would do it:

_handleInput(): void {
    this.onChange(this.value);
  }

The getter of @Input value involves some additional logic that turns the object into a class instance.

Maybe I should create an issue in their github repo.

Hey tobi,
Yes, that’s looks weird. I would say that the getter also adds some validation rules to that mere object.
As I discovered, it was originally added as it is right now https://github.com/angular/components/commit/adf9b5b9783f44d503ef68733bbbaaea816206ac#diff-7a0066ee4355eb71cc26bbe987ce142dR82-R88
The docs says: “This property allows someone to set or get the value of our control.”

I think it would be better to ask Angular material team about their implementation details.

1 Like

ok, done: https://github.com/angular/components/issues/18537

let’s see what they say

1 Like

great, thanks! if you find out something, maybe you could produce an article about the findings, we can publish it :slight_smile:

1 Like

Hello! After playing around with the StackBlitz example, I’ve found out that the onChange function from

  _handleInput(): void {
    this.onChange(this.parts.value);
  }

is an empty function. To try it out:

  • open dev tools + CTRL + P and type form-field-custom-control-example.ts
  • place breakpoints on lines 121 and 112

After refreshing the StackBlitz browser, you should notice that

    registerOnChange(fn) {
        this.onChange = fn;
    }

was not called.

I’d say this happens because in form-field-custom-control-example.html, example-tel-input is used as a standalone form control. Try wrapping into a form and apply a form-control-based(e.g: FormControlName, NgModel) directive to it and you should see the debugger stopping at line 112. This is because the registerOnChange is set with the help of such directive. Source.

So that’s why this.onChange does not produce any results in this case.

Now, if you’re planning to use such custom control in a form, there are a few approaches to this. One approach is to use it as a FormControl, e.g:

<example-tel-input required ngModel name="foo"></example-tel-input>

If inside example-tel-input you’re only dealing with a simple, standalone, form control(bound to an input for instance), I’d say using ControlValueAccessor is a good way to do so. However, if your custom control is composed of multiple form controls(as it happens in the StackBlitz), I’d refrain from using ControlValueAccessor because you’d now have to manage the state of this internal tree yourself.

Imagine you have a form like this:

// FG - FormGroup; FC - FormControl
   FG(root)
  /     \ 
FC    FC(example-tel-input)

IMO, a FormControl should not have any descendants. If it has, it should become a FormGroup(with either ngModelGroup or formGroupName) or a FormArray(formArrayName).

If your custom form control(example-tel-input) is meant to group other form-controls, by using the ControlValueAccessor approach, you’d end up having something like this:

FG(root)
  /     \ 
FC    FC(example-tel-input)
       /   |    \ 
    FC  FC FC

I’d associate with the Shadow Tree.

In order to avoid having to deal with multiple nested trees, you can easily integrate your custom component into an existing tree with: viewProviders: [{ provide: ControlContainer, useExisting: NgForm | FormGroupDirective }].
A great article that explains it has been written by @yurzui: Angular: Nested template driven form.

1 Like

great, thanks! if you find out something, maybe you could produce an article about the findings, we can publish it :slight_smile:

I applied my suggestion of using the value getter to my own code and can now deal with a class instance instead of a mere object in the form that uses my custom form control.

I think this is very useful as the custom form control encapsulates logic that you do not want to deal with elsewhere. Also the custom form control already does some validation, returning null if the input is invalid.

I am happy to share this but probably the focus should be a little broader than just this. It think custom form controls help you write an application that can deal with primitive and complex values, but complex values can be treated as if they were simple because the complexity is taken care of on another level.

yeah, great

I am happy to share this but probably the focus should be a little broader than just this

do you have an idea of how to make it broader and more general?

do you have an idea of how to make it broader and more general?

We are developing an Angular library with UI components for different types of values backed by our CMS. Although all the types have their specifics, they share some commonalities. So I thought it would be a good idea to make all the components subclasses of an abstract base class, so that some basic logic does not have to be repeated over and over again.

The base class defines two form controls: one for the value itself and one for an optional comment field, so that a user can comment on a value.

Some of our types are simple (a number, a string), but some are complex, e.g. an interval consisting of a start and an end number. The interval has to be implemented using a single FormControl, everything else would break the structure of the base class. So implementing a custom ControlValueAccessor allows us to handle the interval like a simple value. You can find more details here: https://github.com/dasch-swiss/knora-ui-ng-lib/issues/4.

The base class defines the public API common to all value components so CRUD components could be implemented in a generic way (there is not need to create a CRUD component for each value type).

I think the specifics relating to our CMS are not interesting for people outside of our group, but maybe the design of the UI components has some broader relevance. Also testing could be interesting as code defined in the base class can be tested in one subclass (component spec) in more detail, and those details do not have to be redundantly repeated in other subclass.

So if you think this could be interesting, I would be happy to write an article about this.

1 Like

Yes, absolutely! I’m planning on writing a series about UI components design patterns in React using Algolia’s React wrappers. I plan spending considerable part discussing topics like HoC used to share logic and provide customization. Maybe we could have a similar article on Angular about design decisions using your CMS components as an example?

Sounds great! We still need some time to finish our implementation though. Maybe we could discuss the article’s concept via Videochat in the upcoming week?

1 Like

sure, ping me at m.koretskyi@gmail.com

ok, solved by: https://github.com/angular/components/pull/18609

1 Like

Hey, thanks for the great article! :+1:I’ve found that the link to forwardRef article is broken (leads to angularindepth)