3

I am using a component-based mat stepper component to display a linear process. Each step have own component as below

<mat-card>
    <mat-horizontal-stepper [linear]="isLinear" labelPosition="bottom" #stepper>
    
    <!-- Step-1 -->
    <mat-step [stepControl]="firstFormGroup">
       <ng-template matStepLabel>Select Items</ng-template>
       <select-item-component>
       <select-item-component>
       <div class="mt-5">
          <button mat-flat-button color="primary" matStepperNext>Next</button>
       </div>
    </mat-step>
    
    <!-- Step-2 -->
    <mat-step [stepControl]="firstFormGroup">
       <ng-template matStepLabel>Add Quantity</ng-template>
       <add-qty-component>
       <add-qty-component>
       <div class="mt-5">
          <button mat-flat-button color="primary" matStepperNext>Next</button>
       </div>
    </mat-step>
    
    <!-- Step-3 -->
    <mat-step [stepControl]="firstFormGroup">
       <ng-template matStepLabel>Conform</ng-template>
       <conform-step-component>
       <conform-step-component>
       <div class="mt-5">
          <button mat-flat-button color="primary" matStepperNext>Done</button>
       </div>
    </mat-step>
    </mat-horizontal-stepper>
 </mat-card>

Step-1 shows the multi selectable list of items and pass selected item list to the next step-2 and add a quantity of each item in step-2.

How to pass selected items on Next click from step-1 to step-2 and display passed item to enter a quantity in step-2?

I have created a common service layer to set and get selected items. ngOnInit of a component of step-2 trying to get the selected list from common service but issue is component-2 is already initiated before the next click.

Can do initialize or re-initialize the second component after the click of next in step-1?

How to display the selected items list in step-2 after moving from step-1?

What will be the best approach for the above scenario?

Just a link to any reference that can answer my question, it should be enough.

Thank you.

Vijay Vavdiya
  • 1,249
  • 13
  • 17

4 Answers4

8

To make a component to output a value use @Output.

To consume data from outside a component, use @Input.

As I don't know the type of your item I'll use ItemType in this example.

select-item-component

Inside select-item-component, declare an attribute:

@Output() onDataChange: EventEmitter<ItemType> = new EventEmitter();

and when the select changes, just do

this.onDataChange.emit(formValue);

add-qty-component

@Input() item: ItemType;

If you want to trigger some actions when the value changes, use set. For example:

@Input() set item(value: ItemType) {
  this.loadOptions(value);
}

You could do the same in select-item-component with select1 and select2 variables.

parent

Use the output and the input values.

...
<select-item-component (onDataChange)="select1 = $event"></select-item-component>
<select-item-component (onDataChange)="select2 = $event"></select-item-component>

...

<add-qty-component [item]="select1"></add-qty-component>
<add-qty-component [item]="select2"></add-qty-component>
...
adrisons
  • 3,443
  • 3
  • 32
  • 48
  • I am assuming the components are not linked as parent-child so if we use input and output decorator won't that create an unnecessary relation between the components ? – gourav Jun 23 '22 at 05:53
  • the parent component would be the one containing the stepper, called "mat stepper component" in the question – adrisons Jun 23 '22 at 09:55
4

You can use a combination of @Output and @Input decorators.

@Output decorator can be used in the child component (stepper 1) to transmit the data to its parent component.

@input decorator can be used to send that data from the parent component to the child component (stepper 2).

You can read more about it here.

Abhishek
  • 1,302
  • 10
  • 18
1

You can use ControlContainer for the same purpose which allows you to access the form controls both in the child component as well as in the parent component

<button mat-raised-button (click)="isLinear = !isLinear" id="toggle-linear">
  {{!isLinear ? 'Enable linear mode' : 'Disable linear mode'}}
</button>
<mat-horizontal-stepper [linear]="isLinear" #stepper>
  <mat-step [stepControl]="firstFormGroup">
    <form [formGroup]="firstFormGroup">
      <ng-template matStepLabel>Fill out your name</ng-template>
     <step1></step1>
      <div>
        <button mat-button matStepperNext>Next</button>
      </div>
    </form>
  </mat-step>
  <mat-step [stepControl]="secondFormGroup">
    <form [formGroup]="secondFormGroup">
      <ng-template matStepLabel>Fill out your address</ng-template>
      <step2></step2>
      <div>
        <button mat-button matStepperPrevious>Back</button>
        <button mat-button matStepperNext>Next</button>
      </div>
    </form>
  </mat-step>
</mat-horizontal-stepper>

Your Step1 component should look like

import {Component, OnInit} from '@angular/core';
import {ControlContainer, FormGroup, Validators} from '@angular/forms';


@Component({
  selector: 'step1',
  template:`
  <form [formGroup]="form"> 
   <mat-form-field>
        <mat-label>Name</mat-label>
        <input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
      </mat-form-field>
  </form>
  `
})
export class Step1 implements OnInit {
  isLinear = false;
  form: FormGroup;

  constructor(private controlContainer:ControlContainer) {}

  ngOnInit() {
   this.form=<FormGroup>this.controlContainer.control;
   this.form.valueChanges.subscribe(data=>console.log(data));
  }
}

Your Step2 component will look like

import {Component, OnInit} from '@angular/core';
import {ControlContainer, FormGroup, Validators} from '@angular/forms';

/**
 * @title Stepper overview
 */
@Component({
  selector: 'step2',
  template:`
  <form [formGroup]="form"> 
    <mat-form-field>
        <mat-label>Address</mat-label>
        <input matInput formControlName="secondCtrl" placeholder="Ex. 1 Main St, New York, NY"
               required>
      </mat-form-field>
  </form>
  `
})
export class Step2 implements OnInit {
  form: FormGroup;

  constructor(private controlContainer:ControlContainer) {}

  ngOnInit() {
   this.form=<FormGroup>this.controlContainer.control;
   this.form.valueChanges.subscribe(data=>console.log(data));
  }
}

stackblitz

Aravind
  • 40,391
  • 16
  • 91
  • 110
0

Another option is to use a shared service. I would use a Subject on the service, and call next in the component that changes the data, passing the new data on the next call.

public $itemsAdded: Subject<Item> = new Subject<Item>();

Then in the next component, subscribe to your Subject and set your local variable to that data.

this.sharedService.$itemsAdded.subscribe(items => {
  this.items = items.map(x => mapItem());
});
Brad Firesheets
  • 762
  • 1
  • 6
  • 20
  • 1
    For preventing unwanted behavior, you either should use a replaySubject, oder a behaviorSubject. Since otherwise it would be easy, to call next on $itemsAdded and subscribing to the subject afterwards. With a behaviorSubject, you should not have this problem. If you do not have an initial value for the *Subject, you can use replaySubject like this: `public $itemsAdded: ReplaySubject = new ReplaySubject(1)` – Eweren Mar 10 '20 at 11:50