Angular Material Stepper reusable form with separate steps as components

1.9k views Asked by At

I'm using Angular Material stepper and it works fine as a whole code in 1 component. But stepper will be used in ~10 different components. There are 7 different types of form like input, select, name, table, etc. So I figured out that I will divide stepper into separate components where each component is a step. So, I decided to make a template for form app-step-form

<form [formGroup]="myForm">
  <mat-vertical-stepper [linear]="isLinear" #stepper>
      <ng-content></ng-content>
  </mat-vertical-stepper>
</form>

Than creating different steps just like that in app-table-select

  <mat-step [stepControl]="tableControl">
    <ng-template matStepLabel>Table </ng-template>
    <mat-form-field appearance="fill">
      <mat-label>Choose table</mat-label>
      <mat-select [formControl]="tableControl">
        <mat-option *ngFor="let table of tables" [value]="table.value">
          {{table.viewValue}}
        </mat-option>
      </mat-select>
    </mat-form-field>
    <div>
      <button mat-button matStepperNext>Next</button>
    </div>
  </mat-step>

and than insert components as steps like that

<app-step-from>

  <app-table-select>
  </app-table-select>

  <app-name-input>
  </app-name-input>

</app-step-from>

So I would have template for form to use in different components just not to create one by one each time.

But the problem I am facing is if I use step component like showed above it is not visible in app-step-form. Making small change in app-step-form and extracting <mat-step></mat-step> to this

<form [formGroup]="myForm">
  <mat-vertical-stepper [linear]="isLinear" #stepper>
    <mat-step>
      <ng-content></ng-content>
    </mat-step>
  </mat-vertical-stepper>
</form>

and removing mat-step from and putting it in <ng-template> like this

<ng-template>
    <ng-template matStepLabel>Table </ng-template>
    <mat-form-field appearance="fill">
      <mat-label>Choose table</mat-label>
      <mat-select>
        <mat-option *ngFor="let table of tables" [value]="table.value">
          {{table.viewValue}}
        </mat-option>
      </mat-select>
    </mat-form-field>
</ng-template>

makes this step visible in app-step-form but cannot pass step component label from <ng-template matStepLabel>Table </ng-template> and this label is not visible or using @Input() neither.

I've spent 2 days trying to crack this up, seeking for an answear but so far not so godd and lost idea how to overcome this problem to make my stepper reusable.

Does anyone have any idea how to make this idea work? Or is it not possible to make the way I figured it?

Also any ideas how to make those inputs act like part of 1 form? Subforms merged to 1 form or what?

1

There are 1 answers

0
Michał Sawicki On

For whom it may concern - I did not find any workaround of this issue.

I've tried to create 1 reusable component with every possible input in it and display each input in it via *ngIf directive depending on path where it is located, for example:

<app-step-form [path]='/your-path'></app-step-from> and each path having each possible inputs but it turned out that it has lot's of unexpected vulnerables. Many fields, that were not required made empty insert to database unnecessarily(where You could and should avoid), so...

I've decided to create each form separately using each independent components by:

    this.parentForm = this.fb.group({
      records: new FormControl(null,
        [
          Validators.required,
          Validators.min(2),
          Validators.max(255),
          Validators.pattern('^(0|[1-9][0-9]*)$')
        ],
        ),
      value: new FormControl(null),
    });

Each input as component had provided validators in it so no worries about validation pf field in each form. Example:

<form [formGroup]="parentForm">
  <mat-vertical-stepper [linear]="isLinear" #stepper>
    <mat-step [stepControl]="parentForm">
      <ng-template matStepLabel>Records</ng-template>
      <app-material-input [parentForm]="parentForm"></app-material-input>
      <div>
        <app-mat-stepper-next-button></app-mat-stepper-next-button>
      </div>
    </mat-step>
    <mat-step [stepControl]="parentForm">
      <ng-template matStepLabel>Value</ng-template>
      <app-material-input [parentForm]="parentForm"></app-material-input>
      <div>
        <app-mat-stepper-next-button></app-mat-stepper-next-button>
      </div>
    </mat-step>
    <mat-step>
      <ng-template matStepLabel>We're done!</ng-template>
      <app-mat-stepper-back-button></app-mat-stepper-back-button>
      <button mat-flat-button (click)="onSubmit(parentForm)">Submit</button>
    </mat-step>
  </mat-vertical-stepper>
</form>

parentForm in parent component: parentForm: FormGroup

in child component: @Input() parentForm: FormGroup;

And that's it. It could be done with 1 form displaying inputs depending on parameters or path but it makes more sense to create separate form via FormBuilder rather than having 1 form for every place that requires it.