angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.37k stars 6.75k forks source link

bug(MatStepper): MatStep’s `completed` should be a 2 way binding #20215

Open paulferaud opened 4 years ago

paulferaud commented 4 years ago

Reproduction

https://stackblitz.com/edit/angular-zxzjbv

Steps to reproduce: The goal is to load a stepper, where some steps are (conditionally) pre-completed.

For example: Step2: Complete, Step3: Not Complete

  1. Create a stepper, with multiple steps. Each step has a "completed" binding to a variable.
  2. Set some of these bindings to true, and others to false.
  3. “Walk” to step 2, step 3, then back to step 2 and step 1.

Expected Behavior

Step 3 should now be complete.

Actual Behavior

Step 3 is not complete, and is hard locked into a completed=false state.

Requests

Each step should have a (completed) output even. For 2 reasons:

  1. We can have a 2 way binding with initial state. Eg:
<mat-step [(completed)]="completed"

And it is then easy to set the initial state in ngOnInit to whatever.

  1. There is currently (AFAIK) no way to react on a step being completed. Note that listening to step change events does not work, because with a linear stepper with invalid steps, clicking on next will complete the step, without actually moving to another step.

Workaround

Our current workaround is to add a @ViewChildren(MatStep) matSteps and then manually do matSteps[1].completed = true;

However, this requires a lot of extra code, and it’s never clear if the child really expects its properties to be changed directly that way.

More importantly though, you can visibly see the stepper load in a non-completed state, and only visibly transition to completed once all the step contents have been loaded.

Environment

jelbourn commented 4 years ago

Marking this as "needs discussion" because there's a broader conversation that needs to happen around which things we support two-way bindings on.

crisbeto commented 4 years ago

It might not be straightforward to turn it into a two-way binding, because by default the value is a getter that takes a couple of different properties into account: https://github.com/angular/components/blob/master/src/cdk/stepper/stepper.ts#L185

paulferaud commented 4 years ago

I’m looking at the code in more details, and it seems that the @Input completed sets a _completedOverride variable, which is then “set in stone”: https://github.com/angular/components/blob/master/src/cdk/stepper/stepper.ts#L182

I could be mistaken, but I would say that the bug is that this is not the expectation? Generally, components can override their inputs when change (for example, and input with a value="some text"). I don’t need 2 way binding. I just need (and I think it’s the expectation) the input only gives initial state, or is only taken into account on change.

On a side note, the reset functions seems to clear the _completedOverride variable. https://github.com/angular/components/blob/master/src/cdk/stepper/stepper.ts#L221 That also sounds like bug, in that it is now ignoring the value of the input. Unless by “initial state” we mean “unconfigured” state. But that does not sound like that was the goal.

paulferaud commented 4 years ago

Actually, further looking at the code, I don’t think I even want completed="true". Rather, I want to mark the step as interacted: https://github.com/angular/components/blob/master/src/cdk/stepper/stepper.ts#L497

There does not seem to be any public API to do this though? Trying to do this.step.interacted = true inside ngAfterViewInit does not seem to work either. EDIT: Actually, that works. But it’s not something that can be done on ngOnInit though, so the flicker is still there.

Would adding a:

// Sets the interacted state of the stepper
@Input interacted: boolean

Sound reasonable?

mrmokwa commented 4 years ago

Perhaps, related to https://github.com/angular/components/issues/20068

paulferaud commented 4 years ago

Perhaps, related to #20068

Sounds related (though not exactly the same). But both cases seem to be asking to be able to set the interacted state.

Khaaz commented 3 years ago

What is the status on that? I would also love two-way binding for completed. I typically need to initialise a step as completed or not optionally. But I still want the component to manage completed state (because of forms and required fields). And I want to be able to see the completed state as I go. Typically I want to trigger an event on the matStepperNext button. But I want this event to only trigger if the step could actually be completed. In which case I would like to use the completed property. I am working around all that but this feature would probably make it a lot easier. If I am missing something obvious here, let me know!

angular-robot[bot] commented 2 years ago

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

angular-robot[bot] commented 2 years ago

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.