Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using separate components in a linear mat-horizontal-stepper

In Angular, is it possible to have a linear stepper where the individual steps are separate components? For example:

<mat-horizontal-stepper [linear]="isLinear">
    <mat-step [stepControl]="firstFormGroup" label="Some Form">
        <first-component></first-component>
    </mat-step>
    <mat-step [stepControl]="secondFormGroup" label="Another Form">
        <second-component></second-component>
    </mat-step>
    <mat-step [stepControl]="thirdFormGroup" label="Review">
        <third-component></third-component>
    </mat-step>
</mat-horizontal-stepper>

When I try this, I receive the following error upon hitting the matStepperNext button:

TypeError: Cannot read property 'invalid' of undefined.

like image 470
Michael Avatar asked Nov 15 '17 17:11

Michael


3 Answers

You can use sub-forms to resolve it. I actually gave a talk a few months ago in Angular-UP conference about it: https://www.youtube.com/watch?v=sb7tgsNF2Jk

The idea, in general, is to create the form in the child component, inject the controlContainer using DI and setting the local form to be the controlContainer form.

Child Component:

 @Component({
  selector: 'app-company-info',
  templateUrl: './company-info.component.html',
  styleUrls: ['./company-info.component.scss'],
  viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class CompanyInfoComponent implements OnInit {

  form: FormGroup;
  constructor(
    private ctrlContainer: FormGroupDirective,
    private fb: FormBuilder) { }

  ngOnInit() {

    this.form = this.ctrlContainer.form;

    this.form.addControl('company',
      this.fb.group({
        'companyName': this.fb.control(null, [Validators.required]),
        'numOfEmployees': this.fb.control(null, [Validators.required])});

  }

}

Parent Component (html):

<mat-horizontal-stepper [linear]="isLinear" #stepper>
  <mat-step [stepControl]="companyInfo">
    <ng-template matStepLabel>Fill out your name</ng-template>

    <form [formGroup]="companyInfo">
      <app-company-info></app-company-info>
    </form>

    <div>
      <button mat-button matStepperNext>Next</button>
    </div>
  </mat-step>
</mat-horizontal-stepper>

Parent Component (ts):

export class WizardComponent implements OnInit {

  isLinear = true;
  companyInfo: FormGroup;

  constructor(private _formBuilder: FormBuilder) {

  }

  ngOnInit() {

    this.companyInfo = this._formBuilder.group({
    });

  }

}
like image 87
Eliran Eliassy Avatar answered Sep 28 '22 18:09

Eliran Eliassy


Here is the solution that work for me.

<mat-horizontal-stepper [linear]="true" #stepper>
  <mat-step [stepControl]="selectAdvType">
    <ng-template matStepLabel>
      <div class="text-center">
        <mat-icon>queue_play_next</mat-icon><br /><span>Select Adv Type</span>
      </div>
    </ng-template>
    <app-advertisement-type></app-advertisement-type>
  </mat-step>
  <mat-step [stepControl]="selectAdvType">
    <ng-template matStepLabel>
      <div class="text-center">
        <mat-icon>directions_car</mat-icon><br /><span>Select Car</span>
      </div>
    </ng-template>
    <app-select-car-adv></app-select-car-adv>
  </mat-step>
  <mat-step>
    <ng-template matStepLabel>
      <div class="text-center">
        <mat-icon>description</mat-icon><br /><span>Select Features</span>
      </div>
    </ng-template>
    <div>
      <button mat-button matStepperPrevious>Back</button>
      <button mat-button (click)="stepper.reset()">Reset</button>
    </div>
  </mat-step>
</mat-horizontal-stepper>

Parent Ts file

@Component({
  selector: 'app-customer.create.advertisement',
  templateUrl: './customer.create.advertisement.component.html',
  styleUrls: ['./customer.create.advertisement.component.scss']
})
export class CustomerCreateAdvertisementComponent implements OnInit {
  isLinear = false;
  selectAdvType: FormGroup;
  constructor(private _formBuilder: FormBuilder) { }
  ngOnInit() {
    this.selectAdvType = this._formBuilder.group({
      firstCtrl: ['', Validators.required]
    });
  }
}

Child component

<form [formGroup]="firstFormGroup">
    <ng-template matStepLabel>Fill out your name</ng-template>
    <mat-form-field>
        <input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
    </mat-form-field>
    <div>
        <button mat-button matStepperNext>Next</button>
    </div>
</form>


@Component({
  selector: 'app-advertisement-type',
  templateUrl: './advertisement-type.component.html',
  styleUrls: ['./advertisement-type.component.scss']
})
export class AdvertisementTypeComponent implements OnInit {
  firstFormGroup: FormGroup;
  constructor(private _formBuilder: FormBuilder) { }

  ngOnInit() {
    this.firstFormGroup = this._formBuilder.group({
      firstCtrl: ['', Validators.required]
    });
  }

}
like image 31
San Jaisy Avatar answered Sep 28 '22 18:09

San Jaisy


Improving on @eliran-eliassy answer and @christian-steinmeyer question.

Parent.Component.ts

  export class ParentComponent implements OnInit {

  isLinear = true;
  companyInfo: FormGroup;

  constructor(private _formBuilder: FormBuilder) {

  }

  ngOnInit() {

    this.companyInfo = this._formBuilder.group({
    });

  }

}

Parent.Component.html

<mat-horizontal-stepper [linear]="isLinear" #stepper>
    <mat-step [stepControl]="companyInfo">
        <form [formGroup]="companyInfo">
            <ng-template matStepLabel>Fill out your name</ng-template>
            <app-company-info></app-company-info>
        </form>
        <div>
            <button mat-button matStepperNext>Next</button>
        </div>
    </mat-step>
</mat-horizontal-stepper>

Child.Component.ts -> this is the sub-form

export class ChildComponent implements OnInit {
  form: FormGroup;
  subForm: FormGroup;
  constructor(
    private ctrlContainer: FormGroupDirective,
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    this.subForm = this.fb.group({
      companyName: [null, [Validators.required]],
      numOfEmployees: [null, [Validators.required]]
    });
    
    this.form = this.ctrlContainer.form;
    this.form.addControl("company", this.subForm);
  }
}

Child.Component.html

<div [formGroup]="subForm">
    <mat-form-field appearance="outline">
        <input matInput placeholder="Your Company Name" formControlName="companyName">
  </mat-form-field>
</div>

See this solution on stackblitz

like image 25
Bembem Avatar answered Sep 28 '22 19:09

Bembem