子组件的Angular Form验证

时间:2017-07-25 13:28:12

标签: forms angular typescript angular-components

我写了一个动态表单,其中有一个主要部分和子部分基于在主要部分(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。

3 个答案:

答案 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!");
    }
  }