我写了一个动态表单,其中有一个主要部分和子部分基于在主要部分(widget.type)中选择的类型。显示和隐藏子部分是使用ngSwitch完成的。
表单的HTML如下所示:
<form class="widget-form cc-form" (ngSubmit)="saveChanges()" novalidate>
<div class="forms-group">
<label for="title" i18n="@@title">Titel</label>
<input class="form-control" id="title" name="title" type="text" [(ngModel)]="widget.title" required />
</div>
<div class="forms-group">
<label class="checkbox-label" for="show" i18n>
<input id="show" name="show" type="checkbox" [(ngModel)]="widget.show" /> <span>Titel tonen in app</span>
</label>
</div>
<div class="forms-group">
<label for="type" i18n="@@type">Type</label>
<select class="form-control" id="type" name="type" [(ngModel)]="widget.type" required>
<option value="text-widget" i18n="@@Text">Tekst</option>
<option value="tasklist-widget" i18n="@@Tasklists">Takenlijst</option>
<option value="image-widget" i18n="@@Text">Afbeelding(en)</option>
<option value="video-widget" i18n="@@Video">Youtube</option>
<option value="link-widget" i18n="@@Link">Link</option>
<option value="contacts-widget" i18n="@@Contacts">Contactpersonen</option>
<option value="attachment-widget" i18n="@@Attachments">Bijlage(n)</option>
</select>
</div>
<ng-container [ngSwitch]="widget.type">
<text-widget *ngSwitchCase="'text-widget'" [data]="widget"></text-widget>
<tasklist-widget *ngSwitchCase="'tasklist-widget'" [data]="widget"></tasklist-widget>
<image-widget *ngSwitchCase="'image-widget'" [data]="widget"></image-widget>
<video-widget *ngSwitchCase="'video-widget'" [data]="widget"></video-widget>
<link-widget *ngSwitchCase="'link-widget'" [data]="widget"></link-widget>
<contacts-widget *ngSwitchCase="'contacts-widget'" [data]="widget"></contacts-widget>
<attachment-widget *ngSwitchCase="'attachment-widget'" [data]="widget"></attachment-widget>
</ng-container>
</form>
每个小部件都是它自己的组件。
问题是表单验证仅检查主要部分的输入并忽略子部分(窗口小部件组件)。如何确保小部件的输入字段包含在验证中?
我尝试将isValid()方法添加到窗口小部件组件,但我无法获取组件的实例,可能是因为它们在ngSwitch中使用。 @ContentChild,@ ContentChildren,@ ViewChild等都返回undefined。
答案 0 :(得分:6)
希望我不会太迟。我最近在模板方法上偶然发现了这个问题,因为反应形式不适合我需要做的事情......
该问题与您的组件需要实现的ControlValueAccessor
有关。但是,我无法做到这一点。
请参阅:https://github.com/angular/angular/issues/9600
andreev-artem提供的解决方案运行良好,我还添加了我的解决方案,将其包含在ngModelGroup
内而不是form
的根对象controls
属性中。
对于您的情况,您未使用ngModelGroup
,您可以使用此指令
@Directive({
selector: '[provide-parent-form]',
providers: [
{
provide: ControlContainer,
useFactory: function (form: NgForm) {
return form;
},
deps: [NgForm]
}
]
})
export class ProvideParentForm {}
用法:在[(ngModel)]
添加指令之前,在根元素的组件中。例如:
<div provide-parent-form>
<input name="myInput" [(ngModel)]="myInput">
</div>
现在,如果您在控制台中输出form object
,或者在controls
对象的form
属性下可以看到您的组件控件。<\ n / p>
答案 1 :(得分:3)
对于未来的Google员工,
我有一个类似的问题,尽管子组件较少,并且在主题上浏览了@penleychan前面提到的线程后,我发现了一个不需要执行自定义指令的小宝石,可以为我解决这个问题。
import { ControlContainer, NgForm } from '@angular/forms';
@Component({
....
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
这适用于我的嵌套表单。只需将其添加到组件中, 哪些直接包含输入
https://github.com/angular/angular/issues/9600#issuecomment-522898551
答案 2 :(得分:0)
决定在子组件上使用isValid方法,该方法指示窗口小部件是否已正确填充。只有在表单和窗口小部件组件都有效时才能保存表单。
所有窗口小部件组件都实现了一个IWidgetComponent接口,该接口需要更改的EventEmitter属性和isValid方法。其中一个子窗口小部件组件如下所示。
@Component({
selector: 'video-widget',
templateUrl: './video.component.html',
styleUrls: ['./video.component.css'],
providers: [YouTubeIdExistsValidator]
})
export class VideoComponent implements OnInit, OnDestroy, IWidgetComponent {
@Input("data")
widget: IWidget;
@Output("change")
changed = new EventEmitter<any>();
video: any;
modelChanged: Subject<string> = new Subject<string>();
public isValid(): boolean {
return this.widget.youtube_id && this.widget.youtube_id !== "" && this.video ? true : false;
}
constructor(private youtubeService: YoutubeService) {
this.modelChanged
.debounceTime(500) // wait 500ms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(youtube_id => this.getYoutubeVideo(youtube_id));
}
ngOnDestroy(): void {
this.widget.youtube_id = "";
}
getYoutubeVideo(youtube_id: string) {
this.youtubeService
.getById(youtube_id)
.subscribe((video) => {
this.video = video;
// Indicate that video was changed
this.changed.emit();
}, (error) => {
this.video = null;
});
}
youtubeIdChanged(youtube_id: string) {
this.modelChanged.next(youtube_id);
}
ngOnInit() { }
}
父html看起来像这样:
<form #widgetForm novalidate>
...
<ng-container [ngSwitch]="widget.type">
<text-widget #ref *ngSwitchCase="'text-widget'" [data]="widget" (change)="saveChanges()"></text-widget>
<tasklist-widget #ref *ngSwitchCase="'tasklist-widget'" [data]="widget" (change)="saveChanges()"></tasklist-widget>
<image-widget #ref *ngSwitchCase="'image-widget'" [data]="widget" (change)="saveChanges()"></image-widget>
<video-widget #ref *ngSwitchCase="'video-widget'" [data]="widget" (change)="saveChanges()"></video-widget>
<link-widget #ref *ngSwitchCase="'link-widget'" [data]="widget" (change)="saveChanges()"></link-widget>
<contacts-widget #ref *ngSwitchCase="'contacts-widget'" [data]="widget" (change)="saveChanges()"></contacts-widget>
<attachment-widget #ref *ngSwitchCase="'attachment-widget'" [data]="widget" (change)="saveChanges()"></attachment-widget>
</ng-container>
...
</form>
每次窗口小部件更改时,都会发出一个事件(this.changed.emit()),该事件触发父组件中的保存窗体方法。在这个方法中,我检查表单和窗口小部件是否有效,如果是,则可以保存数据。
saveChanges() {
if (this.ref && this.ref.isValid() && this.widgetForm.valid) {
// save form
this.toastr.success("Saved!");
}
else {
this.toastr.warning("Form not saved!");
}
}