components icon indicating copy to clipboard operation
components copied to clipboard

Stepper (selectionChange) vs (selectionChanged) and [selectedIndex]

Open philip-firstorder opened this issue 6 years ago • 28 comments

Bug, feature request, or proposal:

    <mat-horizontal-stepper [linear]="false" #stepper 
        [selectedIndex]="this.selectedIndex" 
        (selectionChange)="selectionChange($event)">
      <mat-step [completed]="false" label="First">
        <button mat-button (click)="goto(1)">Next</button>
      </mat-step>
      <mat-step [completed]="false" label="Second">
        <button mat-button (click)="goto(0)">Previous (cancelled)</button>
      </mat-step>
    </mat-horizontal-stepper>

In the stepper control the (selectionChange) event cannot be used together with [selectedIndex], because changing the [selectedIndex] also triggers (selectionChange), so there is no way of cancelling the selection programatically, or implement other logic 'before' the change is executed.

Expected it to work as a standard html select control where:

  • when changing the .selectedIndex the (change) event doesn't get automatically triggered
  • the (change) event is triggered 'before' the change is finished, so it can be cancelled with event.preventDefault()

What is the expected behaviour?

  • The current event should be renamed (selectionChanged) = 'after' the step has changed.
  • Emit a new event (selectionChange) = 'before' the step has changed, with option to cancel the action, which doesn't get automatically triggered when selectedIndex changes.

What is the current behavior?

selectionChange = 'after' the step has changed, naming is confusing, gets triggered automatically with [selectedIndex]

What are the steps to reproduce?

screen shot 2018-02-01 at 12 54 07

  1. Select step 2
  2. Press Previous button (the action is cancelled programatically) - ok
  3. Press step 1 header (step 1 is still selected, the action cannot be cancelled, selectedIndex remains on step 2)

StackBlitz: https://stackblitz.com/edit/angular-material2-issue-9lskal

What is the use-case or motivation for changing an existing behavior?

Be able to programatically enable/disable certain steps

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Material 5.1.0

Is there anything else we should know?

philip-firstorder avatar Feb 01 '18 11:02 philip-firstorder

I think we decided to name our @Output()s across the library as *Change in order to work with 2-way binding syntax. It does seem like probably a bug that this emits when the selectedIndex binding changes though. The events should only be emitted if the user changed the selection

mmalerba avatar Feb 15 '18 23:02 mmalerba

+1

nitin7dc avatar May 26 '18 10:05 nitin7dc

What is the expected behaviour? The current event should be renamed (selectionChanged) = 'after' the step has changed. Emit a new event (selectionChange) = 'before' the step has changed, with option to cancel the action, which doesn't get automatically triggered when selectedIndex changes.

We are living in an async world, canceling an action needs to be implemented as a callback. With the above proposal change you would also break existing installations for the selectionChange behaviour.

I would like to propose the following:

Leave the behaviour as it is for simple usage to get notified of a selection change. Add an option (e.g. [navigation]="'component'", or 'api') to the stepper that all navigation is done by the component. On klick of stepper header or buttons the component receives StepperSelectionEvent and manipulates selectedIndex, directly or by using next() and prev(). Alternatively to an additional option an additional @Output (e.g. beforeSelectionChange) could be used for api usage, to define a component callback and to identify to the stepper that the component takes care of all navigation.

The original post is 6 months old, without any fixes/enhancements proposed. I haven't used material before and need something like a stepper. Does somebody now of a similar stepper library (bootstrap support) ? Does somebody have a fork fixing this issue?

FritzHerbers avatar Aug 16 '18 12:08 FritzHerbers

hey, this is the official doc from stepper? https://material.angular.io/components/stepper/overview i was looking for something like @ouput (selectionChange) there but couldnt find, theres another documentation for mat stepper?

leonardopaiva avatar Sep 10 '18 18:09 leonardopaiva

@leonardopaiva You need to look in the CDK docs of the Stepper. https://material.angular.io/cdk/stepper/api

philip-firstorder avatar Sep 11 '18 05:09 philip-firstorder

thanks @philip-firstorder

leonardopaiva avatar Sep 11 '18 18:09 leonardopaiva

anyone have a workaround for this? I need to disable the change trigger when I set the selectedIndex programatically.

bmdatl avatar Sep 12 '18 17:09 bmdatl

@mmalerba (being assigned for this issue) @crisbeto (for your recent commits of Stepper)

There is a need for an API for Stepper. The discussion is stalled atm. I hope you can comment on how to fix or implement such API resulting in an acceptable PR.

FritzHerbers avatar Sep 21 '18 12:09 FritzHerbers

Made a change to the CDK stepper source working for 6.4.7 and 7-beta, see diff: stepper.zip

Build with: npm run build

In your project use a npm link in package.json to reference the release build:

"@angular/cdk": "file:../../project/material2/dist/releases/cdk",

Usage:

<mat-vertical-stepper [selectedIndex]="selectedIndex" "(beforeSelectionChange)="beforeSelectionChange($event)">

beforeSelectionChange callback receives the new selectedIndex, set selectedIndex in your Component to change the Step.

The source can be used to create a PR.

FritzHerbers avatar Sep 24 '18 12:09 FritzHerbers

@FritzHerbers Your patch works great. Our project got really stuck due to this issue, your patch helped us to get on track again, thanks. We hope it gets patched soon, so getting rid of the local cdk clone.

vivulazi avatar Oct 03 '18 12:10 vivulazi

We really need this beforeSelectionChange event as well as we're using a notifications service to inform the user of validation errors (using ngx-toastr). It is easy to show the messages when clicking the 'next' button because we can tap into that event but if the user clicks on the header of the next step (instead of the 'next' button) we have no way of showing the errors.

asmolenic avatar Nov 09 '18 09:11 asmolenic

Has there been any progress on this suggestion? Any active pull request for an implementation? It feels like this suggested functionality would be an appropriate addition to the CDK stepper. Many apps I have worked on benefit from using a stepper-like flow on several screens, but the form dependent validation doesn't really make sense in several of those use cases.

Having hook(s) in place for "before a selection change is completed" in order to back out of the step change and notify the user of any errors would be extremely beneficial.

One specific use case I have run into in the past consists of a stepper containing three steps. The first step is fine working with form validation as we were only collecting information. The second step however does not only collect information, but it takes a portion of that information, and makes an API request to validate some of their input within a 3rd party system before we transition them to the next step. Currently this sort of use case doesn't appear to be fully supported out of the box due to the fact that the step labels could still be clicked to progress to the next step. We were obviously able to stop the transition using the [completed] binding on the step, but providing the user with appropriate feedback proved to be more challenging than maybe was necessary (unless they clicked on a custom button we bound to an action wrapping a call to next()).

In the end we had to abandon using the stepper entirely for the above project and approach it a different way in order for our end users not to have a bit of a confusing experience.

jdhackn avatar Dec 12 '18 17:12 jdhackn

Is this method added in the new angular 7? As per earlier comment by @FritzHerbers, it was added in beta 7 Made a change to the CDK stepper source working for 6.4.7 and 7-beta

Maneabhi avatar Feb 05 '19 15:02 Maneabhi

Has there been a pull request opened with the suggested changes posted above?

jonjnicholson avatar Feb 18 '19 22:02 jonjnicholson

PR for this issue: https://github.com/angular/material2/pull/15604

l46kok avatar Mar 26 '19 06:03 l46kok

What is the status for this feature? It's been more then a year.

akvaliya avatar Apr 12 '19 08:04 akvaliya

Is there any progress on the event 'beforeSelectionChanged' ? It would be very usefull

CarlosBustos1 avatar May 05 '19 19:05 CarlosBustos1

I struggled with this in few days. Was able to achieve a workaround as setting the stepControl status to pending when every internal form validation all passed. Then once user clicks on stepper header, I capture the click event to do something and once the job finished, I set the stepControl status to valid and let the stepper moves programmatically by setting new value for selectedIndex

nhducseuit avatar Aug 20 '19 09:08 nhducseuit

Any solution for before selection change? I am having same requirement

ghost avatar Oct 08 '20 10:10 ghost

I am looking for a solution too! Would be even nice to capture the events and handle it the way I like. That could save me re-implement a lot of code!

szygyorgy avatar Dec 12 '20 10:12 szygyorgy

Any changes on this??? Even in 2022 there is still the exact same problem....

spoilerdo avatar Jun 28 '22 07:06 spoilerdo

@FritzHerbers what is the status of the 'beforeSelectionChanged' event? The Angular Material v13 still doesn't have your fix.

rammaks avatar Aug 17 '22 22:08 rammaks

It's been almost four years and there is no fix for this. Is there any updates on this? I don't even think there is a need for creating another event for this, all needed is to add an additional boolean property to the StepperSelectionEvent lets call it 'Cancel' that can be set to 'true' in SelectionChange event hanlder to prevent the step change.

When SelectionChange event is triggerd the UI is not updated yet. That tells me that this is actually fired before the step change happens. So why not add a property to give the developer an oportunity to cancel or stop propogating it in the event chain. I can't find any real workaround to this, the Stepper SelectionChange happens before the button click event so there is no way to stop propagation. The only thing I can use is the previous() function of the Stepper to nevigate back to the previous step from the event handler and that fires the event twice. I can live with that but it would realy be good to provide an elegant solution for this.

arman-g avatar Sep 22 '22 20:09 arman-g

Keeping the dream alive with another vote begging for a solution. I monkeypatched the select method on MatStep in the meantime.

The need to extend, manipulate, and mutate the behavior of UI elements is predictable and reasonable, this really needs a solution.

cmbkla avatar Jan 18 '23 14:01 cmbkla

In the mean time, this has been a successful solution for my current project. We grossly patch the built-in select method of each individual step and introduced a call to a method that may asynchronously decide whether the specified forward transition is allowed by returning a true or false from an Observable.

If you manipulate the steps after load by adding, removing, or otherwise changing their order, this might get a little gnarly. But if you have a relatively straightforward linear stepper, this will give you a place for "in between" step action. On the UI I built a small loading spinner/message element within each step that displays when the beforeSelectionAllowedToChange is doing it's work, a loading spinner overlay may also be appropriate for your use-case.

Using this shim, if you have added Next/Previous buttons to the stepper you will want to bind a click handler to those and have them call step.select() on the appropriate step rather than stepper.next() etc, so the buttons use the same process as the header navigation.

    protected shimStepper(): void {
        this.stepper.steps.forEach(s => {
            if (!s["__stepperShimmed"]) {
                const oldSelect = s["select"].bind(s);
                s["select"] = () => {
                    // if the step isn't valid, do nothing
                    if (this.stepper.selected.stepControl.status === "INVALID") {
                        oldSelect();
                        return;
                    }

                    let clickedStepIndex: number = -1;
                    let currentStepIndex: number = -1;
                    this.stepper.steps.forEach((item: MatStep, idx: number) => {
                        if (item === s) {
                            clickedStepIndex = idx;
                            return;
                        }
                        if (this.stepper.selected === item) {
                            currentStepIndex = idx;
                        }
                    });

                    // only call the before selection change method if the direction is forward
                    if (clickedStepIndex > currentStepIndex) {
                        this.beforeSelectionAllowedToChange(
                            currentStepIndex,
                            clickedStepIndex
                        ).subscribe(result => {
                            if (result) {
                                oldSelect();
                            }
                        });
                        return;
                    }
                    oldSelect();
                };
                s["__stepperShimmed"] = true;
            }
        });
    }

cmbkla avatar Jan 18 '23 14:01 cmbkla

Any update?

thewebchameleon avatar Jul 29 '23 10:07 thewebchameleon

Any updates on the current issue ?

hatimgheewala-GIG avatar Aug 23 '23 08:08 hatimgheewala-GIG

Bump, we are still using the gnarly patch. A little frustrating; my org has offered two pull requests to address this issue, several years ago, and they were closed for stylistic opposition. If the style was the only problem, it seems like it should be simple to solve. Or, we could accept variation with the knowledge it may change in the future when someone has a better idea, in the meantime, this issue languishes as a solved problem.

cmbkla avatar May 16 '24 22:05 cmbkla