最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - What is the relationship of NG_VALUE_ACCESSOR, ControlValueAccessor interface in angular? - Stack Overflow

programmeradmin5浏览0评论

What is the relationship of NG_VALUE_ACCESSOR, ControlValueAccessor interface in angular? Is if I have the following statement (provide:NG_VALUE_ACCESSOR)

@Component({
  selector: 'rm-header',
  templateUrl: './record-managerponent.html',
  styleUrls: ['./record-managerponent.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => RecordManagerComponent),
    multi: true
  }]
})

then I already implement ControlValueAccessor, no need to explicitly writes.

export class ComponentA implements ControlValueAccessor

but then I still need to implement: writeValue(), registerOnChange(), registerOnTouched(), and setDisabledState() defined in ControlValueAccessor interface?

What is the relationship of NG_VALUE_ACCESSOR, ControlValueAccessor interface in angular? Is if I have the following statement (provide:NG_VALUE_ACCESSOR)

@Component({
  selector: 'rm-header',
  templateUrl: './record-managerponent.html',
  styleUrls: ['./record-managerponent.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => RecordManagerComponent),
    multi: true
  }]
})

then I already implement ControlValueAccessor, no need to explicitly writes.

export class ComponentA implements ControlValueAccessor

but then I still need to implement: writeValue(), registerOnChange(), registerOnTouched(), and setDisabledState() defined in ControlValueAccessor interface?

Share Improve this question edited Mar 26 at 4:08 Naren Murali 60.1k5 gold badges44 silver badges77 bronze badges asked Mar 26 at 3:11 user1169587user1169587 1,3962 gold badges19 silver badges44 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

What is the relationship?

NG_VALUE_ACCESSOR is the InjectionToken Angular uses to inject the value accessors. The ControlValueAccessor is the contract (aka interface) that every value accessor should implement.

Why do you need an Injection Token?

Injection tokens are required because the interfaces are gone once Angular's compiler translates the typescript code to JavaScript. There is no concept of an "interface" in JS.

There are different ways to define a provider. The most common one is when you simply define the class.

providers: [ MyClass ] // this is just a shorthand for { provide: MyClass, useClass: MyClass }

But when you want to provide an interface or a value, you need to use an injection token.

Do I need to implement ControlValueAccessor

Yes, you have to. That is the contract. Angular will call each method during the lifecycle of the custom form control.

From the source code, this is my explanation as on (26/3/2025), source code changes are inevitable, by as on this date, this is the working (As per my understanding).

When you do

export class ComponentA implements ControlValueAccessor

control_value_accessor.ts - Source Code

You force the user to define writeValue, registerOnChange, registerOnTouched and optionally setDisabledState.

These need to be defined, because internally these methods are used for writing a value and listening to changes on input.

default_value_accessor.ts - Source Code.

Now we know the significance of ControlValueAccessor (enforce the user to properly define the methods required for interfacing the form state with the DOM state). If you define the methods without the interface, it will still work. Because the interface is just to enforce proper definition, not to do anything functional.

control_value_accessor.ts:13 - Source Code

/**
 * @description
 * Defines an interface that acts as a bridge between the Angular forms API and a
 * native element in the DOM.
 *
 * Implement this interface to create a custom form control directive
 * that integrates with Angular forms.
 *
 * @see {@link DefaultValueAccessor}
 *
 * @publicApi
 */

Provider NG_VALUE_ACCESSOR:

In the source of ngModel, we can see that we are looking up NG_VALUE_ACCESSOR to an array property, then calling a shared function (selectValueAccessor)

ng_model.ts#L234 - Source Code

  constructor(
    ...
    @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
    ...
  ) {
    ...
    this.valueAccessor = selectValueAccessor(this, valueAccessors);
  }

Now observe that since you have provided NG_VALUE_ACCESSOR as the actual component itself, the valueAccessor will be the actual component you have defined. The forwardRef is to avoid circular dependencies error.

On looking at the below code that sets up the value accessor. We can see that this method distinguishes between defaultAccessor (A directive that is used internally by angular component to interact with the DOM) or BuiltInControlValueAccessor ( Handles the DOM interactions for controls that need to behave differently compared to regular input. E.g: checkbox, range selection, etc. ) and finally our customControlValueAccess (The control we are defining so you are responsible for defining the DOM binding).

shared.ts:386 - Source Code

// TODO: vsavkin remove it once https://github/angular/angular/issues/3011 is implemented
export function selectValueAccessor(
  dir: NgControl,
  valueAccessors: ControlValueAccessor[],
): ControlValueAccessor | null {
  if (!valueAccessors) return null;

  if (!Array.isArray(valueAccessors) && (typeof ngDevMode === 'undefined' || ngDevMode))
    _throwInvalidValueAccessorError(dir);

  let defaultAccessor: ControlValueAccessor | undefined = undefined;
  let builtinAccessor: ControlValueAccessor | undefined = undefined;
  let customAccessor: ControlValueAccessor | undefined = undefined;

  valueAccessors.forEach((v: ControlValueAccessor) => {
    if (v.constructor === DefaultValueAccessor) {
      defaultAccessor = v;
    } else if (isBuiltInAccessor(v)) {
      if (builtinAccessor && (typeof ngDevMode === 'undefined' || ngDevMode))
        _throwError(dir, 'More than one built-in value accessor matches form control with');
      builtinAccessor = v;
    } else {
      if (customAccessor && (typeof ngDevMode === 'undefined' || ngDevMode))
        _throwError(dir, 'More than one custom value accessor matches form control with');
      customAccessor = v;
    }
  });

  if (customAccessor) return customAccessor;
  if (builtinAccessor) return builtinAccessor;
  if (defaultAccessor) return defaultAccessor;

  if (typeof ngDevMode === 'undefined' || ngDevMode) {
    _throwError(dir, 'No valid value accessor for form control with');
  }
  return null;
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论