【问题标题】:Angular reactive form returns empty values when fields are untouched未触及字段时,Angular 反应式表单返回空值
【发布时间】:2019-09-03 20:38:06
【问题描述】:

在我的用例中,当用户单击编辑按钮时,Angular 会对后端进行 HTTP 调用并检索对象,然后在 EDIT 表单中填充这些值。用户可以更新或保持字段不变。当单击update 按钮时,Angular 应该获取表单中存在的所有这些值并将它们发送到后端。所以,这就是问题所在,在将值加载到编辑页面表单并更新某些字段并保持某些字段未触及后,未触及的值会变为空。这真的很奇怪

product-edit.component.html

<div *ngIf="productDataAvailable()">
  <h2>Update Product</h2>
  <br/>
  <form [formGroup]="productForm">
    <div class="form-group">
      <label for="id">Product Id</label>
      <input class="form-control" formControlName="id" id="id" type="text" value="{{product.id}}">

      <small class="form-text text-muted" id="emailHelp"></small>
    </div>
    <div class="form-group">
      <label for="name">Name</label>
      <input class="form-control" formControlName="name" id="name" type="text" value="{{product.name}}">
    </div>
    <div class="form-group">
      <label for="description">Description</label>
      <input class="form-control" formControlName="description" height="100" id="description" required type="text"
             [value]="product.description" width="200">
    </div>
    <div class="form-group">
      <label for="currency">Price</label> <br/>
      <label>
        <select (load)="loadCurrencies()" class="form-control"  [value]="product.price.currency.symbol" formControlName="currency" id="currency" name="currency">
          <option *ngFor="let currency of currencies" value={{currency.id}}>
            {{currency.name}}
          </option>
        </select>
      </label>
      <input formControlName="price" id="price" required style="margin: 10px; padding: 5px" type="text" [value]="product.price.amount">
    </div>

    <div class="form-group">
      <label>Category:
        <select (load)="loadCategories()" class="form-control" formControlName="category" name="category">
          <option  [value]="category.id" *ngFor="let category of categories">
            {{category.name}}
          </option>
        </select>
      </label>
    </div>

    <div class="form-group">
      <label>Manufacturer:
        <select (load)="loadManufacturers()" class="form-control" [value]="product.manufacturer.name" formControlName="manufacturer" name="manufacturer">
          <option [value]="manufacturer.id" *ngFor="let manufacturer of manufacturers" >
            {{manufacturer.name}}
          </option>
        </select>
      </label>
    </div>

    <button (click)="updateProduct()" class="btn btn-primary" type="submit">Submit</button>
    <button (click)="goBack()" class="btn btn-primary" style="margin-left: 30px" type="button">Cancel</button>

  </form>

</div>

ProductEditComponent

import {Component, OnInit} from '@angular/core';
import {Product} from '../model/product';
import {ProductService} from '../service/product.service';
import {ActivatedRoute, Router} from '@angular/router';
import {CATEGORY_API_URL, CURRENCY_API_URL, MANUFACTURER_API_URL, PRODUCT_API_URL, SERVER_URL} from '../../../app.constants';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {Price} from '../model/price';
import {Currency} from '../model/currency';
import {Category} from '../../category/model/category';
import {Manufacturer} from '../model/manufacturer';
import {CategoryService} from '../../category/service/category.service';

@Component( {
              selector: 'app-product-edit',
              templateUrl: './product-edit.component.html',
              styleUrls: ['./product-edit.component.css']
            } )
export class ProductEditComponent implements OnInit
{
  product: Product;
  categories: Array<Category>;
  currencies: Array<Currency>;
  manufacturers: Array<Manufacturer>;

  productForm=new FormGroup( {
                               id: new FormControl( {value: '', disabled: true}, Validators.minLength( 2 ) ),
                               name: new FormControl( '' ),
                               description: new FormControl( '' ),
                               price: new FormControl( '' ),
                               category: new FormControl( '' ),
                               currency: new FormControl( '' ),
                               manufacturer: new FormControl( '' )
                             } );

  constructor(private productService: ProductService,
              private categoryService: CategoryService,
              private route: ActivatedRoute,
              private router: Router)
  {
  }

  ngOnInit()
  {

    this.getProduct();
    this.loadCategories();
    this.loadCurrencies();
    this.loadManufacturers();
  }

  productDataAvailable(): boolean
  {
    return this.product!==undefined;
  }

  goBack()
  {
    this.router.navigate( ['/product'] );
  }

  private getProduct()
  {
    const id=this.route.snapshot.paramMap.get( 'id' );
    const url=SERVER_URL+PRODUCT_API_URL+'find/'+id;
    this.productService.getProductDetails( url ).pipe()
        .subscribe(
          data =>
          {
            this.product=data;
          },
          error =>
          {
            console.log( error );
          },
          () => console.log( 'getProduct() success' ) );
  }

  private updateProduct()
  {
    const id=this.route.snapshot.paramMap.get( 'id' );
    const url=SERVER_URL+PRODUCT_API_URL+'update';

    const product=new Product();
    product.id=Number( id );
    product.name=this.productForm.get( 'name' ).value;
    product.description=this.productForm.get( 'description' ).value;
    const currency=new Currency( this.productForm.get( 'currency' ).value, 'USD', '$' );
    product.price=new Price(currency , this.productForm.get( 'price' ).value );
    product.category=new Category( this.productForm.get( 'category' ).value );
    product.manufacturer=new Manufacturer( this.productForm.get( 'manufacturer' ).value );
    product.lastModifiedBy='Admin';
    product.lastModifiedDate='Admin';

    this.productService.updateProduct( url, product ).subscribe(
      value =>
      {
        console.log( 'Successfully updated product' );
      }, error1 =>
      {
        console.log( 'Failed to update product' );
      },
      () =>
      {
        this.router.navigate( ['/product/list'] );
      } );
  }

  private loadCategories()
  {
    const url=SERVER_URL+CATEGORY_API_URL+'list';

    this.categoryService.getCategories( url ).subscribe(
      categories =>
      {
        // @ts-ignore
        this.categories=categories;
        console.log( 'Successfully loaded categories' );
      },
      error1 =>
      {
        console.log( 'Failed to load categories' );
      },
      () =>
      {
      } );
  }

  private loadCurrencies()
  {
    const url=SERVER_URL+CURRENCY_API_URL+'list';

    this.productService.getCurrencies( url ).subscribe(
      currencies =>
      {
        this.currencies=currencies;
      },
      error1 =>
      {
        console.log( 'Failed to load currencies' );
      },
      () =>
      {
      } );
  }

  private loadManufacturers()
  {
    const url=SERVER_URL+MANUFACTURER_API_URL+'list';

    this.productService.getManufacturers( url ).subscribe(
      manufacturers =>
      {
        this.manufacturers=manufacturers;
        console.log( 'Successfully loaded manufacturers' );
      },
      error1 =>
      {
        console.log( 'Failed to load manufacturers' );
      },
      () =>
      {
      } );
  }
}

角度版本

Angular CLI: 7.3.8
Node: 10.15.0
OS: darwin x64
Angular: 7.2.12
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.13.8
@angular-devkit/build-angular     0.13.8
@angular-devkit/build-optimizer   0.13.8
@angular-devkit/build-webpack     0.13.8
@angular-devkit/core              7.3.8
@angular-devkit/schematics        7.3.8
@angular/cli                      7.3.8
@ngtools/webpack                  7.3.8
@schematics/angular               7.3.8
@schematics/update                0.13.8
rxjs                              6.4.0
typescript                        3.2.4
webpack                           4.29.0

【问题讨论】:

  • 您错过了反应式表单和一般 Angular 工作原理的全部要点。真相在模型中,而不在视图中。如果您希望表单控件具有值,则将值存储在此表单控件的模型中(即设置 FormControl 对象的值)。你不使用value="{{product.id}}"。修改模型会修改视图。在输入中输入内容会修改模型。见angular.io/guide/reactive-forms#replacing-a-form-control-value(我链接到一个特定的部分,但你最好阅读整个指南)
  • 有什么理由不使用ngModel 双向绑定?我是 Angular 新手,如果这很明显/没有帮助,请原谅
  • @AndrewAllen ngModel 用于模板驱动的表单。使用响应式表单时不要使用它。

标签: angular angular6 angular7 angular-httpclient


【解决方案1】:

据我所知,您已发出 HTTP 请求以从您的服务器获取数据,但您没有以正确的方式填充您的 productForm FormGroup。由于您使用的是响应式表单,我强烈建议您使用 patchValuesetValue 来更新您的 FormControls。

对于您的情况,我会推荐patchValue,因为它比setValue 更灵活。 patchValue 不需要在参数中指定所有 FormControl 来更新/设置表单控件的值。

这就是您可以使用patchValue 的方式。在您的getProduct() 方法上,您可以通过执行此操作将data 响应中的属性从getProductDetails() 传递到您的FormGroup;

getProduct() {
  const id = this.route.snapshot.paramMap.get('id');
  const url = SERVER_URL + PRODUCT_API_URL + 'find/' + id;
  this.productService.getProductDetails(url).pipe()
    .subscribe(
      data => {
        this.productForm.patchValue({
          id: data.id
          name: data.name
          description: data.description
          // other form fields
        })
      },
      error => {
        console.log(error);
      },
      () => console.log('getProduct() success'));
}

此外,在您的模板 html 上,无需在每个 &lt;input&gt;&lt;select&gt; 上绑定您的 value 属性。您可以删除所有这些。这是因为,您已经在使用 patchValue 更新值。

<div class="form-group">
  <label for="name">Name</label>
  <input class="form-control" formControlName="name" id="name" type="text">
</div>
<div class="form-group">
  <label for="description">Description</label>
  <input class="form-control" formControlName="description" height="100" id="description" required type="text" width="200">
</div>

当您需要从 productForm 获取数据时,可以使用在 FormGroup 和 FormControls 上公开的 value 属性。

updateProduct() {
  const id = this.route.snapshot.paramMap.get('id');
  const url = SERVER_URL + PRODUCT_API_URL + 'update';

  //console.log(this.productFrom.value) 
  const product = this.productForm.value

  this.productService.updateProduct(url, product).subscribe(
    value => {
      console.log('Successfully updated product');
    }, error1 => {
      console.log('Failed to update product');
    },
    () => {
      this.router.navigate(['/product/list']);
    });
}

【讨论】:

  • @JBNizet 对不起,我复制了 OP 的代码,因为我是懒惰的 AF。将其删除
  • 我想我尝试了类似版本的解决方案。让我再试一次。
  • 我们如何处理选择下拉菜单? &lt;select class="form-control" formControlName="currency" id="currency" name="currency"&gt; &lt;option *ngFor="let currency of currencies" value={{currency.id}}&gt; {{currency.name}} &lt;/option&gt; &lt;/select&gt; 在这种情况下,我需要遍历选项并将 currency.id 设置为选项值。我应该在哪里做这个?在 HTML 或组件中?我认为不可能把它放在 HTML 中。请告诉我
  • 1.应该是[value]="currency.id"。 2.这个选择框的FormControl应该包含选择的值,即应该选择的crrency的ID。
  • @Jadda 很高兴为您提供帮助!是的,JB Nizet 是正确的。您需要将其绑定到value 属性,该属性可以是字符串或数字。如果需要将其绑定到对象,则需要使用ngValue 而不是value
猜你喜欢
  • 2020-09-21
  • 2017-05-14
  • 1970-01-01
  • 2020-04-25
  • 2020-04-02
  • 2020-01-01
  • 2020-05-13
  • 2018-11-13
  • 2014-05-08
相关资源
最近更新 更多