【问题标题】:Mixing Reactive Form with Template Form混合反应形式和模板形式
【发布时间】:2019-09-08 09:45:04
【问题描述】:

我使用模板表单构建了一个包含大量输入的大型表单。现在我需要动态添加一部分输入。由于使用 Reactive Form 动态添加输入似乎更容易,我想将输入的特定部分更改为 Reactive Form。

那么是否可以在同一个表单标签中混合使用响应式表单和模板表单?

【问题讨论】:

  • 是的,这是可能的,但我不确定它的正确或错误方法。
  • @Abhishek 我用谷歌搜索,但没有找到任何关于它的文章。
  • 我想说,快速试一试并检查一下,在不知不觉中,您将切换到反应式。无论如何,它作为更复杂的形式要求的解决方案被推向市场。您可以在这里找到一些原因:为什么不建议将两者混合:blog.angular-university.io/…
  • @suhailvs 我注意到你已经提供了赏金。您介意提供更多详细信息,以便我为您提供解决方案吗?
  • @wentjun 我正在使用 Angular 6.0.3,但以后可能会更新到 7.x.x。所以我认为坚持使用 Reactive Forms 并从中删除 ngModels 会更好

标签: angular angular-reactive-forms angular-template-form


【解决方案1】:

摘自我上面发布的链接/https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/

部分:但是 ngModel 发生了什么?

请注意,ngModel 仍然可以与响应式表单一起使用。只是表单值在两个不同的地方可用:视图模型和 FormGroup,这可能会导致一些混乱。

【讨论】:

    【解决方案2】:

    您可以混合使用reactive forms 和模板驱动的表单,但强烈不建议这样做。这是因为在响应式表单上使用ngModel 违背了表单状态不变性的理念。

    响应式表单的原则遵循“单向”数据绑定规则,您可以遵循一种不可变的方法来管理表单的状态,从而在模板和组件逻辑之间实现更大的关注点分离。您可以在第一段的链接中阅读有关反应式表单优势的更多信息。

    假设您要混合使用模板驱动的表单和响应式表单。运行ng serve时控制台会抛出如下错误:

    您似乎在与 formControlName 相同的表单字段上使用 ngModel。 Angular v6 中已弃用对使用 ngModel 输入属性和 ngModelChange 事件的响应式表单指令的支持,并将在 Angular v7 中删除 有关这方面的更多信息,请在此处查看我们的 API 文档:https://angular.io/api/forms/FormControlName#use-with-ngmodel

    【讨论】:

      【解决方案3】:

      是的,您可以先将两者一起使用,然后创建一个反应式表单,然后根据您的要求添加模板驱动。请参阅角度文档如何一起使用两者

      【讨论】:

      • ERROR 错误:“ngModel 不能用于使用父 formGroup 指令注册表单控件。请尝试改用 formGroup 的合作伙伴指令“formControlName”。示例:
      【解决方案4】:

      是的,你可以,检查这个link 它是完整的反应形式,但它接近你的场景,但你需要做的是进行一些修改以匹配你的情况。 就我而言,我所做的是:

      1- 在我的表单中添加以下标签:

      //反应式表单的标签应该在模板驱动表单内

      
         <mat-tab [label]="'Invoices' | localize">
              <mat-card-content [formGroup]="exampleForm">
            <!-- Start form units array with first row must and dynamically add more -->
            <mat-card formArrayName="units"  >
              <mat-card-title>Units</mat-card-title>
              <mat-divider></mat-divider>
      
              <!-- loop throught units -->
              <div *ngFor="let unit of exampleForm.controls.units.controls; let i=index" >
      
                <!-- row divider show for every nex row exclude if first row -->
                <mat-divider *ngIf="exampleForm.controls.units.controls.length > 1 && i > 0" ></mat-divider><br>
      
                <!-- group name in this case row index -->
                <div [formGroupName]="i">
                  <div fxLayout="row" fxLayout.xs="column" fxLayoutWrap fxLayoutGap="3.5%" fxLayoutAlign="center">
      
                    <!-- unit name input field -->
                    <mat-form-field  fxFlex="30%"> 
                      <input matInput placeholder="Unit name" formControlName="unitName" required>  
                      <!-- input field error -->
                      <mat-error *ngIf="unit.controls.unitName.invalid">
                          Unit name is required.        
                      </mat-error>            
                    </mat-form-field>
      
      
                    <!-- unit quantity input field -->
                    <mat-form-field  fxFlex="10%" fxFlex.xs="20"> 
                      <input matInput placeholder="Quantity" type="number" formControlName="qty" required>
                    </mat-form-field>
      
                    <!-- unit price input field -->
                    <mat-form-field  fxFlex="20%"  fxFlex.xs="grow"> 
                      <input matInput placeholder="Unit price" type="number" formControlName="unitPrice" required>
                    </mat-form-field>
      
                    <!-- unit total price input field, calculated and not editable -->  
                    <div fxLayout.xs="row">
                    <mat-form-field  > 
                      <input matInput placeholder="Total sum" formControlName="unitTotalPrice">
                    </mat-form-field>
      
                    <!-- row delete button, hidden if there is just one row -->
                    <button type="button" mat-mini-fab color="warn" fxFlex="nogrow"
                            *ngIf="exampleForm.controls.units.controls.length > 1" (click)="removeUnit(i)">
                        <mat-icon>delete forever</mat-icon>
                    </button>
                    </div>
                  </div>
                </div>
              </div>
      
              <!-- New unit button -->
              <mat-divider></mat-divider>
              <mat-card-actions>
                <button type="button" mat-raised-button (click)="addUnit()">
                  <mat-icon>add box</mat-icon>
                  Add new unit
                </button>
                <button type="button" mat-raised-button (click)="clearAllUnits()">
                  <mat-icon>remove_circle</mat-icon>
                  Clear all
                </button>
      
              </mat-card-actions>
            </mat-card> <!-- End form units array -->
            <br>
            <!-- Total price calculation formated with angular currency pipe -->
            <mat-card>
              Total price is {{ totalSum | currency:'USD':'symbol-narrow':'1.2-2'}}
            </mat-card>
          </mat-card-content>
      

      2- 及以下在我的 TS 文件中:

      export class CreateSubProjectComponent extends AppComponentBase implements OnInit, AfterViewInit, OnDestroy {
       exampleForm: FormGroup;
        myFormValueChanges$;
        totalSum: number = 0;
      
      constructor(injector: Injector,
      private formBuilder: FormBuilder,
          private currencyPipe: CurrencyPipe){
      super(injector);
      }
      
        ngOnInit() {
      this.exampleForm = this.formBuilder.group({
            units: this.formBuilder.array([
      
               this.getUnit()
            ])
          });
      
      // initialize stream on units
          this.myFormValueChanges$ = this.exampleForm.controls['units'].valueChanges;
      // subscribe to the stream so listen to changes on units
      this.myFormValueChanges$.subscribe(units => this.updateTotalUnitPrice(units));
      
      }//end of ngOnInit
      
      ngAfterViewInit() {}
       ngOnDestroy() { this.myFormValueChanges$.unsubscribe(); }
      
      private getUnit() {
          const numberPatern = '^[0-9.,]+$';
          return this.formBuilder.group({
            unitName: ['', Validators.required],
            qty: [1, [Validators.required, Validators.pattern(numberPatern)]],
            unitPrice: ['', [Validators.required, Validators.pattern(numberPatern)]],
            unitTotalPrice: [{value: '', disabled: true}]
          });
        }
      /**
         * Add new unit row into form
         */
        addUnit() {
          const control = <FormArray>this.exampleForm.controls['units'];
          control.push(this.getUnit());
        }
        /**
         * Remove unit row from form on click delete button
         */
        removeUnit(i: number) {
          const control = <FormArray>this.exampleForm.controls['units'];
          control.removeAt(i);
        }
        /**
         * This is one of the way how clear units fields.
         */
        clearAllUnits() {
          const control = <FormArray>this.exampleForm.controls['units'];
          while(control.length) {
            control.removeAt(control.length - 1);
          }
          control.clearValidators();
          control.push(this.getUnit());
        }
       /**
         * Update prices as soon as something changed on units group
         */
        private updateTotalUnitPrice(units: any) {
          // get our units group controll
          const control = <FormArray>this.exampleForm.controls['units'];
          // before recount total price need to be reset. 
          this.totalSum = 0;
          for (let i in units) {
            let totalUnitPrice = (units[i].qty*units[i].unitPrice);
            // now format total price with angular currency pipe
            let totalUnitPriceFormatted = this.currencyPipe.transform(totalUnitPrice, 'USD', 'symbol-narrow', '1.2-2');
            // update total sum field on unit and do not emit event myFormValueChanges$ in this case on units
            control.at(+i).get('unitTotalPrice').setValue(totalUnitPriceFormatted, {onlySelf: true, emitEvent: false});
            // update total price for all units
            this.totalSum += totalUnitPrice;
          }
        }
      }
      

      this article 反应形式也靠近你的场景

      【讨论】:

        猜你喜欢
        • 2020-04-05
        • 1970-01-01
        • 2017-12-29
        • 1970-01-01
        • 2021-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-18
        相关资源
        最近更新 更多