7

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?

8
  • yes it is possible but i'm not sure about its right or wrong approach. Commented Apr 18, 2019 at 5:17
  • @Abhishek I googled but didn't found any article about it. Commented Apr 18, 2019 at 5: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 : blog.angular-university.io/… Commented Apr 18, 2019 at 5: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? Commented Apr 28, 2019 at 16:06
  • 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 Commented Apr 29, 2019 at 5:27

4 Answers 4

14
+50

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

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.