我需要能够将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
中已定义的功能。
答案 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>
@yurzui接受的答案很简单,简单,并且很好地演示了如何对大多数角形代码进行一些处理。有一部分让我感到困扰,我不想在我的顶级HTML中添加ngDefaultControl
。他的回答使我朝着正确的方向看。
与此同时,@ gjvatsalya谈到了代码重用。例如,当我查看DefaultValueAccessor
时,发现了可处理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>