对于自定义表单组件,是否可以使用DefaultValueAccessor而不是ControlValueAccessor?

时间:2018-05-03 16:11:44

标签: angular

我需要能够将formControlName指令用于我的自定义组件。

我一直在阅读关于为子组件实现ControlValueAccessor的多个SO问题,这一切似乎都非常脆弱。

很多示例都在将<div><span>元素转换为表单元素,因此实现ControlValueAccessor所需的所有功能都是有意义的。

但是,我的组件只使用本机<input>元素。我正在创建一个单独的组件,因为我想在输入中使用一些图标,我显然不想在任何地方复制/粘贴图标css。

我遇到了DefaultValueAccessor类,它似乎是所有原生输入元素的角度所用的。我可以以某种方式对我的自定义组件利用此行为吗?

我只是不想复制此功能。关于错误和各种浏览器行为,可能很难长期维护它。我宁愿只使用已经与原生输入相关联的功能。

修改

这是<jg-search>(我的自定义组件)的代码段:

<div>
    <svg>
        <!-- some content -->
    </svg>
    <label for="search">Search</label>
    <input id="search" type="text"></input>
    <svg>
        <!-- some content -->
    </svg>
</div>

我希望能够以这种方式调用它:<jg-search formControlName="keyword">

这可以通过在SearchComponent中实现ControlValueAccessor来实现。但由于我只使用原生<input type="text">,因此我不想重新实现DefaultValueAccessor中已定义的功能。

2 个答案:

答案 0 :(得分:3)

一种可能的解决方案是在自定义组件上使用ngDefaultControl属性:

<div [formGroup]="form">
    <jg-search formControlName="x" ngDefaultControl></jg-search>
                                   ^^^^^^^^^^^^^^^^
</div>

现在您需要做的就是将input元素与现有FormControl链接,如下所示:

@Component({
  selector: 'jg-search',
  template: `
   <input [formControl]="ngControl.control">
  `
})
export class MyInput {
  constructor(public ngControl: NgControl) {}
}

有关更多信息,请参阅 Ng-run Example

答案 1 :(得分:1)

作为一名表单开发人员,我希望有一种简单的方法将单位附加到输入表单字段中,以便可以最小化HTML代码。

我有如下所示的HTML(带有Bootstrap):

  <div class="input-group">
    <input type="number" formControlName="weight"/>
    <div class="input-group-append">
         <span class="input-group-text">lbs</span>
    </div>
  </div>

我宁愿这样写:

<inp-with-units formControlName="weight" units="lbs"></inp-with-units>

JSON or JSONB datatype

@yurzui接受的答案很简单,简单,并且很好地演示了如何对大多数角形代码进行一些处理。有一部分让我感到困扰,我不想在我的顶级HTML中添加ngDefaultControl。他的回答使我朝着正确的方向看。

与此同时,@ gjvatsalya谈到了代码重用。例如,当我查看DefaultValueAccessor  form control with number (38.2) and units ('lbs')时,发现了可处理AndroidOS移动版sources的代码。我什至不知道这项技术的存在。点制,请尽可能使用Angular。

yurzui的解决方案将DefaultValueAccessor(DVA)放在调用的html上。相反,让我们将其放在内部模板输入DOM元素上,并实现ControlValueAccessor接口以桥接到DVA。为什么将DVA放在HTML模板代码中? (1)必须记住每次都添加ngDefaultControl似乎是出错的机会。 (2)ControlValueAccessor接口不太可能很快更改,如果确实如此,则桥接的代码也需要更改。将来我们不会感到惊讶。

这段代码还有其他作用,它可以确保 View => Model 更改通知如果看起来像数字,则重新键入为number。这暗示了我们可以使用类似此类的<input>包装器进行更有趣的行为。

@Component({
  selector: 'inp-with-units',
  template: `
  <div class="input-group">
    <input type="number" ngDefaultControl/>
    <div class="input-group-append" *ngIf="units.length">
         <span class="input-group-text">{{units}}</span>
    </div>
  </div>
`})
export class InpNumWithUnits implements ControlValueAccessor {
  @Input('units') units: string = "";
  @ViewChild(DefaultValueAccessor, {static: true}) dva: DefaultValueAccessor;

  constructor(@Self() private ngControl: NgControl) {
    this.ngControl.valueAccessor = this;
  }

  // Component uses type 'number'. Angular FormControlDirective handles
  // View => Model type conversion (onChange sends number). We wrap the
  // onChange notifier so we, too, can do a type conversion on the value.
  wrap(fn) { return (v) => fn(v === "" || isNaN(v) ? v : +v) }

  // *** ControlValueAccessor Methods
  setDisabledState(d: boolean) { this.dva.setDisabledState(d); }
  writeValue(value: any)       { this.dva.writeValue(value); }
  registerOnChange(fn: any)    { this.dva.registerOnChange(this.wrap(fn)); }
  registerOnTouched(fn: any)   { this.dva.registerOnTouched(fn); }
}

ETA(2020 08 13):好像有一个CompositionEvent与“自定义控件”相关,并且禁止显示值更改通知。如果您使用的是上述解决方案,并且需要防止价值变化的传播,请予以告知。


ETA(2020 08 28):该错误的解决方法是不使用valueChanges,而是将用户输入事件调用添加到HTML (input)="trackChanges($event)"中。您可以按如下所示添加它:

<inp-with-units formControlName="weight"
                units="lbs"
                (input)="trackChanges($event)">
</inp-with-units>