为了展示一种真实世界的例子,假设我们想在我们的应用程序中使用@ angular/material的datepicker.
我们希望在很多页面上使用它,因此我们希望将它添加到具有相同配置的表单中非常容易.为了满足这一需求,我们在<mat-datepicker>ControlValueAccessor实现周围创建了一个自定义角度组件,以便能够[(ngModel)]在其上使用.
我们希望处理组件中的典型验证,但同时,我们希望为包含我们的外部组件提供验证结果CustomDatepickerComponent.
作为一个简单的解决方案,我们可以实现这样的validate()方法(innerNgModel来自导出的ngModel:.#innerNgModel="ngModel"请参阅本问题末尾的完整代码):
validate() {
return (this.innerNgModel && this.innerNgModel.errors) || null;
}
Run Code Online (Sandbox Code Playgroud)
此时,我们可以以非常简单的方式在任何表单组件中使用datepicker(如我们所愿):
<custom-datepicker [(ngModel)]="myDate"></custom-datepicker>
我们还可以扩展上面的行以获得更好的调试体验(像这样):
<custom-datepicker [(ngModel)]="myDate" #date="ngModel"></custom-datepicker>
<pre>{{ date.errrors | json }}</pre>
Run Code Online (Sandbox Code Playgroud)
只要我更改自定义datepicker组件中的值,一切正常.如果datepicker有任何错误,则周围的表单仍然无效(如果datepicker有效,它将变为有效).
但!
如果myDate外部组件的成员(一个作为ngModel传递)由外部组件(如:)更改this.myDate= null,则会发生以下情况:
writeValue()该CustomDatepickerComponent的运行,并将其更新日期选择器的值.validate()该CustomDatepickerComponent的运行,但在这一点上innerNgModel没有更新,因此返回以前的状态的验证.要解决此问题,我们可以从setTimeout中的组件发出更改:
public writeValue(data) {
this.modelValue = data ? moment(data) : null;
setTimeout(() => { this.emitChange(); }, 0);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,emitChange(自定义组件的广播更改)将触发新的验证.并且由于setTimeout,它将在下一个周期运行,而innerNgModel已经更新.
我的问题是,如果有更好的方法来处理这个问题而不是使用setTimeout? 如果可能的话,我会坚持模板驱动的实现.
提前致谢!
该示例的完整源代码:
定制datepicker.component.ts
import {Component, forwardRef, Input, …Run Code Online (Sandbox Code Playgroud) javascript angular-material angular-validation angular controlvalueaccessor
在我自己实现解决方案之前,我想知道在数据绑定属性值刚刚更改时是否有一种简单的方式来更改元素的样式(简短的突出显示).
我的DOM中有很多元素,所以我不想在组件中存储和维护专用属性.
我要强调的元素是传统的输入形式元素:
<tr field label="Lieu dépôt">
<select class="cellinput" #lieuDepotBon [(ngModel)]="rapport.LieuDepotBon" (ngModelChange)="changeRapport({LieuDepotBon:$event})">
<option [ngValue]="null"></option>
<option [ngValue]="i" *ngFor="let depotBonChoice of DepotBonInterventionValues; let i = index">{{DepotBonIntervention[i]}}</option>
</select>
</tr>
<tr field *ngIf="rapport.LieuDepotBon==DepotBonIntervention.Autre" label="Autre lieu">
<input class="cellinput" #autreLieuDepotBon [(ngModel)]="rapport.AutreLieuDepotBon" (ngModelChange)="changeRapport({AutreLieuDepotBon:autreLieuDepotBon.value})" />
</tr>
Run Code Online (Sandbox Code Playgroud)
我听说过Angular2在元素上使用ngModel指令设置的特殊类样式可以帮助我做我需要但我找不到更多关于它的内容.
使用双向数据绑定时,似乎没有办法观察父组件的变化。
我有一个用于收集标签列表的自定义输入组件。双向数据绑定在此组件与其父组件之间设置和工作。
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
Run Code Online (Sandbox Code Playgroud)
在父组件中,如何观察绑定变量的变化?虽然它总是最新的(我已经确认了这一点),但我找不到任何关于对变化做出反应的指导。
我试过了:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
Run Code Online (Sandbox Code Playgroud)
和
skillTagUpdate(event){
console.log(event); // nothing
}
Run Code Online (Sandbox Code Playgroud)
更新: TWDB 恕我直言不是它所宣传的那样。每当我到达 TWDB 似乎是一个解决方案的地方时,我都会重新设计服务和/或可观察的通信。
我已经在我的应用程序中实现了 Control Value Accessor,但是在运行该应用程序时,我收到以下错误消息:
ERROR TypeError: dir.valueAccessor.writeValue is not a function
at setUpControl (forms.js:2578)
at FormGroupDirective.addControl (forms.js:6318)
at FormControlName._setUpControl (forms.js:6969)
at FormControlName.ngOnChanges (forms.js:6892)
at checkAndUpdateDirectiveInline (core.js:24499)
at checkAndUpdateNodeInline (core.js:35163)
at checkAndUpdateNode (core.js:35102)
at debugCheckAndUpdateNode (core.js:36124)
at debugCheckDirectivesFn (core.js:36067)
at Object.eval [as updateDirectives] (AnnouncementInformationListComponent.html:82)
Run Code Online (Sandbox Code Playgroud)
我是这样设置的:
公告信息列表组件.html
<form [formGroup]="_form">
<div [formArrayName]="item.code">
<div *ngFor="let controlItem of _form.get(item.code).controls; let ctrlIndex = index"
class="form-row"
[formGroupName]="ctrlIndex">
<div [ngClass]="{'col-8': oneParameter(item), 'col-4': twoParameters(item), 'col-3': threeParameters(item)}"
*ngFor="let param of item.parameters; let arrIndex = index">
<div [ngSwitch]="param">
<input *ngSwitchCase="blabla(param)"
type="text" …Run Code Online (Sandbox Code Playgroud) 我有一个自定义输入组件,它实现了 ControlValueAccessor,提供者声明如下。它似乎工作正常。在我可以在互联网上找到的所有教程中,只要提供 NG_VALUE_ACCESSOR,就会广泛使用 forwardRef
将以下代码发送到生产环境是否安全?
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CustomInputComponent, //Notice I omitted forwardRef here and it works fine
multi: true
}]
Run Code Online (Sandbox Code Playgroud) 这是诀窍:
问题:
ControlValueAccessor 接口中的方法 validate() 在值更改后立即调用,并且不等待异步验证器。当然,控制无效且待定(因为正在进行验证)并且主表单也无效且待定。一切正常。
但。当异步验证器完成验证并返回 null(意味着值有效)时,自定义控件将有效并且状态也变为有效,但父级仍然无效且处于挂起状态,因为值访问器的 validate() 没有再次调用。
我试图从 validate() 方法返回 observable,但主表单将其解释为错误对象。
我找到了解决方法:当异步验证器完成验证时,从自定义控件传播更改事件。它迫使主窗体再次调用 validate() 方法并获得正确的有效状态。但它看起来又脏又粗糙。
问题是: 需要做什么才能使父表单由子自定义控件的异步验证器管理?必须说它适用于同步验证器。
所有项目代码都可以在这里找到:https : //stackblitz.com/edit/angular-fdcrbl
主表单模板:
<form [formGroup]="mainForm">
<child-control formControlName="childControl"></child-control>
</form>
Run Code Online (Sandbox Code Playgroud)
主窗体类:
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
@Component({
selector: "my-app",
templateUrl: "./app.component.html"
})
export class AppComponent implements OnInit {
mainForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.mainForm = this.formBuilder.group({
childControl: this.formBuilder.control("")
});
}
}
Run Code Online (Sandbox Code Playgroud)
自定义子控件模板:
<div …Run Code Online (Sandbox Code Playgroud) validation typescript angular angular-reactive-forms controlvalueaccessor
ImageHandler实现ControlValueAccessor.ImageHandler应该在另一个组件中动态生成,并且视图将被嵌入。输入和输出可以毫无问题地设置。但如何应用formControl到嵌入的视图或组件上。<image-handler [formControl]='fControl'></image-handler>
主要问题是这个 formControl 必须应用于图像处理程序本身,否则它可以通过作为fControl输入传递并在ImageHandlerComponent.
当前的解决方法:创建一个WrapperComponentforImageHandler或任何其他类似的组件。这些基于参数的包装器显示带有表单控件的特定元素。
<image-handler *ngIf='isImagehandler' [formControl]='fControl'></image-handler>
...
这个包装组件可以动态创建,没有任何问题。但这似乎不是一个真正的解决方案。
所以我想做一个共享相同 formControlName 的自定义无线电组件。单击单选按钮时,共享相同 formControlName 的其余组件应更新为活动值。
我该怎么做?
javascript typescript angular angular-reactive-forms controlvalueaccessor
我有一个反应形式。设置与此类似:
myForm: FormGroup;
this.myForm= new FormGroup({
name: new FormControl("", [Validators.required, Validators.maxLength(15), Validators.pattern('...')]),
...
});
Run Code Online (Sandbox Code Playgroud)
我在我的表单上使用它,如下所示:
<input
type="text"
formControlName="name"
/>
<div *ngIf="name.errors?.required">
Name is required
</div>
<div *ngIf="name.errors?.maxlength">
Name must be {{ name.errors.maxlength.requiredLength }} characters
</div>
<div *ngIf="name.errors?.pattern">
Name has invalid characters.
</div>
Run Code Online (Sandbox Code Playgroud)
这只是我表格的精简版。我有多个输入,我不得不为每个输入创建错误 div。
所以为了解决这个问题,我尝试创建一个组件。该组件与上面的代码非常相似:
<input
type="text"
[formControlName]="formControlName"
/>
<div *ngIf="name.errors?.required">
Name is required
</div>
etc...
Run Code Online (Sandbox Code Playgroud)
.ts 文件:
@Component({
selector: 'app-text',
templateUrl: './text.component.html'
})
export class TextComponent {
@Input() formControlName: FormControl;
}
Run Code Online (Sandbox Code Playgroud)
所以在我的表单上,我想使用这个组件如下:
<app-text [formControlName]="name"></app-text>
Run Code Online (Sandbox Code Playgroud)
但是我不能让它与 formControlName 属性一起使用。
这可能吗?
谢谢 …
angular-components angular angular-reactive-forms controlvalueaccessor
我已经input在我的 angular 应用程序 use 中创建了一个简单的自定义组件ControlValueAccessor。所以,当我想创建一个表单input元素时,我不必调用<input />,只需调用<my-input>.
我有一个问题,当我使用 时<input />,我可以使用myDirective. 例如:
<input type="text" class="form-control" formControlName="name" myDirective />
Run Code Online (Sandbox Code Playgroud)
但是,当我使用 时my-input,我不能使用myDirective. 例如:
<my-input formControlName="name" myDirective></my-input>
Run Code Online (Sandbox Code Playgroud)
myDirective在我的输入中不起作用
这是my-input组件使用ControlValueAccessor代码:
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'my-input',
templateUrl: './my-input.component.html',
styleUrls: ['./my-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyInputComponent ),
multi: true …Run Code Online (Sandbox Code Playgroud)