加载数据后设置反应形式(异步) - Angular 5

Posted

技术标签:

【中文标题】加载数据后设置反应形式(异步) - Angular 5【英文标题】:Set reactive form after data loaded (async) - Angular 5 【发布时间】:2018-07-19 01:29:18 【问题描述】:

我正在尝试在从 API 加载值后更新表单值。我尝试使用*ngIf 技术,但即使设置了表单,表单也不可见。

我无法分享整个项目,但这里是组件模板和控制器

模板

<div class="page-title">
  <h3> Edit news </h3>
</div>
<div class="partner-add-form">
  <ng-container *ngIf='newsForm'>
    <form action="" [formGroup]='newsForm' (ngSubmit)="onSubmit()">
      <div class="row  ">
        <div class="input-field col s12 tooltip">
          <input formControlName='title' [id]="'title'" [type]="'text'">
          <label [for]="'title'">Title</label>
          <validator-errors [control]='newsForm.get("title")'></validator-errors>
        </div>
        <div class="input-field col s12 tooltip">
          <textarea class="materialize-textarea" formControlName='content' [id]="'content'"></textarea>
          <label [for]="'content'">Content</label>
          <validator-errors [control]='newsForm.get("content")'></validator-errors>
        </div>
        <div class="input-field col s12 margin-reset">
          <mat-form-field class="full-width">
            <mat-select [formControl]='newsForm.controls["partner_id"]'>
              <mat-option disabled selected>Categories</mat-option>
              <mat-option *ngFor="let partner of partners.data" [value]="partner.id">
               partner.name  </mat-option>
            </mat-select>
          </mat-form-field>
          <validator-errors [control]='newsForm.controls["partner_id"]'></validator-errors>
        </div>
        <div class="file-field col s12 input-field">
          <div class="btn">
            <span>File</span>
            <input (change)="fileChangeListener($event)" type="file"> </div>
          <div class="file-path-wrapper">
            <input class="file-path validate" type="text" placeholder="Upload one or more files"> </div>
        </div>
        <div class="col s12">
          <div class="flex flex-middle flex-center crop-area">
            <img-cropper #cropper [image]="data" [settings]="cropperSettings"></img-cropper>
            <i class="material-icons">arrow_forward</i>
            <div class="result rounded z-depth-1">
              <img [src]="data.image " *ngIf="data.image " [width]="cropperSettings.croppedWidth"
                [height]="cropperSettings.croppedHeight"> </div>
          </div>
        </div>
        <div class="col s12 form-bottom">
          <div class="left">
            <button type="button" onclick='window.history.back()' class='btn btn-large waves-effect waves-light '>
              <i class="material-icons">keyboard_arrow_left</i>
              <span>Back</span>
            </button>
          </div>
          <div class="right">
            <button [ngClass]="'disabled':(newsForm['invalid']) || isSubmitting || !data.image " type="submit" class='btn btn-large waves-effect waves-light '>
            Submit </button>
          </div>
        </div>
      </div>
    </form>
  </ng-container>
</div>

控制器

  partners;

  news = ;
  newsForm: FormGroup;

  ngOnInit() 
    setTimeout(() => 
      this._dashboardService.routeChangeStarted();
    , 0);
    this._activatedRoute.params.subscribe(params => 
      this.news["id"] = params["id"];
      this.getPartners().then(data => 
        this.getNews().then(data=>
          this.setForm();
        )
      );
    );
  

  setForm() 
    this.newsForm = this._formBuilder.group(
     title: [this.news['title'], [Validators.required]],
     content: [this.news['content'], [Validators.required]],
     partner_id: [this.news['partner']['id'], [Validators.required]]
    );
    console.log(new Boolean(this.newsForm));
  

  getPartners() 
    return Promise((res, rej) => 
      setTimeout(() => 
        this._dashboardService.progressStarted();
        this._dashboardService.routeChangeStarted();
      , 0);
      this._partnerService.getPartners().subscribe(
        partners => 
          if (partners.status == 200) 
            this.partners = partners;
            res(partners.data);
           else 
            this._errorActions.errorHandler(partners.status);
          
          setTimeout(() => 
            this._dashboardService.progressFinished();
            this._dashboardService.routeChangeFinished();
          , 0);
        ,
        error => 
          this._notificationService.warning("Ups, something went wrong");
        
      );
    );
  

  getNews() 
    setTimeout(() => 
      this._dashboardService.routeChangeStarted();
      this._dashboardService.progressStarted();
    , 0);

    return Promise((res, rej) => 
      this._newsService.getNews(this.news["id"]).subscribe(
        data => 
          if (data["status"] == 200) 
            Object.assign(this.news, data["data"]);
            res(this.news);
           else 
            this._errorActions.errorHandler(data["status"]);
          
          setTimeout(() => 
            this._dashboardService.progressFinished();
            this._dashboardService.routeChangeFinished();
          , 0);
        ,
        error => 
          this._notificationService.error("Ups, something went wrong");
        
      );
    );
  

有什么问题?设置表单后,它甚至不显示表单本身。从 API 加载数据后,还有其他方法可以设置表单值吗?

【问题讨论】:

【参考方案1】:

您可以创建一个单独的函数来用数据填充表单。您可以在从 API 获取数据后调用此函数。

变体 1:setValue

官方角度文档:setValue

使用 setValue,您可以通过传入其属性与 FormGroup 后面的表单模型完全匹配的数据对象来一次分配每个表单控件值。

例子:

updateValues(dataObject: any) 
  this.heroForm.setValue(
    name:    this.hero.name,
    address: this.hero.addresses[0] || new Address()
  );

变体 2:patchValue

官方角度文档:patchValue

使用 patchValue,您可以通过为感兴趣的控件提供键/值对对象来为 FormGroup 中的特定控件分配值。

例子:

updateValues(dataObject: any) 
  this.heroForm.patchValue(
    name: this.hero.name
  );

【讨论】:

嗯更好的解决方案,下次遇到这个异步问题时我会这样做【参考方案2】:

我遇到过这个问题,我不知道我的解决方案是否是最好的,但它确实有效。该技术是使用loaded: boolean,您启动到false,一旦您的数据在您的组件中完全接收,您将其设置为true

这是一个例子:

.html:

<div *ngIf="loaded == false">
    <h2>loading ...</h2>
</div>
<div *ngIf="loaded == true">
   // your template goes here
</div>

在你的 .ts 中:

loaded: boolean = false;

// your code ....

ngOnInit() 
  setTimeout(() => 
    this._dashboardService.routeChangeStarted();
  , 0);
  this._activatedRoute.params.subscribe(params => 
    this.news["id"] = params["id"];
    this.getPartners().then(data => 
      this.getNews().then(data=>
        this.setForm();

        // here is the important part!
        this.loaded = true
      )
    );
  );

【讨论】:

【参考方案3】:

您可以使用setValuepatchValue 为您提供异步数据FormGroup

private initForm(): void 
    // For update product
    if (this.isUpdate) 
      this.productService.getProduct(this.id)
        .subscribe((product: Product) => 
          this.productForm.patchValue(
            name: product.name,
            price: product.price,
            description: product.description,
            image: product.image
          );
        );
    
    // For create product
    this.productForm = new FormGroup(
      name: new FormControl(null, [
        Validators.required,
        Validators.minLength(8),
        Validators.maxLength(35)
      ]),
      price: new FormControl(null, [
        Validators.required,
        Validators.min(5000),
        Validators.max(50000000),
      ]),
      description: new FormControl(null, [
        Validators.required,
        Validators.minLength(8),
        Validators.maxLength(500)
      ]),
      image: new FormControl(null, Validators.required)
    );
  

【讨论】:

很好...谢谢:) 他们来了【参考方案4】:

为了从 Promise 订阅更新表单,我需要手动运行更改检测。这可以通过导入来实现

constructor(private changeDetectorRef: ChangeDetectorRef) 

正在运行

this.changeDetectorRef.detectChanges();

【讨论】:

以上是关于加载数据后设置反应形式(异步) - Angular 5的主要内容,如果未能解决你的问题,请参考以下文章

以反应形式设置 Angular 2 FormArray 值?

Angular 8:反应式表单自动映射和 PatchValue 设置成员数据

如何在测试 Angular 2 反应形式中使用 setValue 和 patchValue

以反应形式设置图像 url 在角度 2 中给出错误

Angular 5-反应形式和动态形式之间的区别

vue树形组件懒加载点击加载后,再次点击没反应