6

I have built a big form with lots of input using Template Form. Now I have got a requirement to add a part of input dynamically. Since adding inputs dynamically seems easier with Reactive Form, I would like to change that specific part of inputs to Reactive Form.

So is it possible to mix reactive forms and template forms in a same form tag?

suhailvs
  • 20,182
  • 14
  • 100
  • 98
  • yes it is possible but i'm not sure about its right or wrong approach. – Abhishek Apr 18 '19 at 05:17
  • @Abhishek I googled but didn't found any article about it. – suhailvs Apr 18 '19 at 05:18
  • I would say, take a quick dip and check it out and before you know it you will switch to reactive. Its being marketed anyway as the solution to more complexe form requirements. you can find some reasons here: why mixing the two is not recommended : https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/ – jcuypers Apr 18 '19 at 05:19
  • @suhailvs I noticed you have put a bounty. Do you mind giving me more details so that I can give you a solution for it? – wentjun Apr 28 '19 at 16:06
  • @wentjun I would like to know whether it is possible to use ngModel in Reactive forms. when i tried i got error `ERROR Error: " ngModel cannot be used to register form controls with a parent formGroup directive. Try using formGroup's partner directive "formControlName" instead. ` – suhailvs Apr 29 '19 at 03:55
  • @suhailvs yes I get it. But I think I have already explained on my answer, that it may not be possible on Angular's newer versions, since they might have already deprecated the combined usage. Just to check, may I know what Angular version are you using? – wentjun Apr 29 '19 at 03:56
  • 1
    @wentjun i am using angular 6.0.3, but i might update to 7.x.x later. So I think it will be better to stick with Reactive Forms and remove the ngModels from it – suhailvs Apr 29 '19 at 05:27
  • @suhailvs Yeah, reactive form should work with ngModel in Angular 6, except it will fire than warning when you are running the application in dev mode (i.e. when you run with `ng serve --open`). The only difference if that, you use ngModel to get/update data on your forms, rather than using built-in reactive form methods. Nonetheless, I would still highly recommend for you to make the transition to reactive forms. – wentjun Apr 29 '19 at 05:35

4 Answers4

13

You can mix both reactive forms and template driven forms, but it is highly not recommended. This is because using ngModel on reactive forms goes against the idea of immutability of the form state.

The principles of reactive forms follows the 'one-way' data binding rule, whereby you follow an immutable method of managing the state of your forms, such that there is greater separation of concern between your template and component logic. You can read more about the advantages of reactive forms on the link at the first paragraph.

Assuming you are going ahead with mixing template driven forms and reactive forms. The console will throw the following error when you run ng serve:

It looks like you're using ngModel on the same form field as formControlName. Support for using the ngModel input property and ngModelChange event with reactive form directives has been deprecated in Angular v6 and will be removed in Angular v7 For more information on this, see our API docs here: https://angular.io/api/forms/FormControlName#use-with-ngmodel

wentjun
  • 40,384
  • 10
  • 95
  • 107
2

Excerpt from the link i have posted above / https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/

Section : But what happened to ngModel?

Note that ngModel can still be used with reactive forms. It's just that the form value would be available in two different places: the view model and the FormGroup, which could potentially lead to some confusion.

jcuypers
  • 1,774
  • 2
  • 14
  • 23
2

Yes,you can use both together create a reactive form first and then add template driven based on your requirement its works.Please refer angular documentation how both can be used together

chrizel
  • 52
  • 1
  • 8
0

Yes you can, check this link it's full reactive form but its near to your scenario, but what you need to do is making some modifications to become matching your case. In my case what I did are :

1- add below tags inside my form :

//below tags for reactive form should be inside the template-driven form


   <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- and below is in my TS file :

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 for reactive form also near to your scenario

Abdulaziz
  • 654
  • 3
  • 12
  • 37