-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(select): add support for custom errorStateMatcher #6147
Conversation
src/lib/core/error/error-options.ts
Outdated
@@ -7,27 +7,24 @@ | |||
*/ | |||
|
|||
import {InjectionToken} from '@angular/core'; | |||
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; | |||
import {FormGroupDirective, NgForm, NgControl} from '@angular/forms'; | |||
|
|||
/** Injection token that can be used to specify the global error options. */ | |||
export const MD_ERROR_GLOBAL_OPTIONS = new InjectionToken<ErrorOptions>('md-error-global-options'); | |||
|
|||
export type ErrorStateMatcher = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I was thinking about maybe refactoring some of this error state matcher stuff because @g1shin recently had to implement a context-sensitive one for the stepper (#6117) and it felt a bit messier than it needed to be... What do you think about something like this:
export interface ErrorStateMatcher {
isErrorState(...): boolean;
}
export class DefaultErrorStateMatcher {
isErrorState(...) { ... }
}
export class ShowOnDirtyErrorStateMatcher {
isErrorState(...) { ... }
}
And then in error module:
@NgModule({
providers: [{provide: ErrorStateMatcher, useClass: DefaultErrorStateMatcher}]
})
And finally in input/select/etc:
constructor(errorStateMatcher: ErrorStateMatcher) {
console.log(errorStateMatcher.isErrorState(...));
}
I like this because:
- We don't have to worry about the function being ripped off the class and losing its context
- We don't have to worry about falling back to the defaultErrorStateMatcher in every component since we provide that in the error module
- The code to inject in the constructor doesn't need a whole bunch of
@Optional() @Inject(...)
stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM, but because the matcher is really just the function, it would look more along the lines of this:
export class ErrorOptions {
isErrorState = (control, form) => control.invalid && (form.submitted || control.dirty);
// more error-related config goes here in the future.
}
Also I'm not sure whether we should provide the ShowOnDirtyErrorStateMatcher
by default since it's pretty basic logic that people can write themselves if they're overriding it anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Is there other stuff we plan to put in ErrorOptions
? Just curious, I can't remember why we went with that over just a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have anything atm, but the intention is to avoid having to add any more providers if we decide to have more options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crisbeto I added it only because it seemed to be a common question here and on SO #4750 (comment), #4750 (comment)
@mmalerba None that I was aware of. Simply based it off of PlaceholderOptions
which seemed a nicely future proof
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok sounds good, I do want to keep the interface though so that components like the stepper can implement it.
interface ErrorOptions { ... }
class DefaultErrorOptions implements ErrorOptions { ... }
edit: @crisbeto I'd like to keep the show-on-dirty one because as @willshowell it was a pretty common request and it's nice to have a really easy way for people to do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, although FWIW, you can implement a class as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waaa I didn't know that, TS you crazy
a4e2e19
to
062ceb9
Compare
@mmalerba I ended up splitting the |
src/lib/core/error/error-options.ts
Outdated
*/ | ||
@Injectable() | ||
export class ErrorOptions { | ||
errorStateMatcher: ErrorStateMatcher = defaultErrorStateMatcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this method style instead? I want it to be obvious that it's safe to use this
in the method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/lib/input/input-container.ts
Outdated
const control = this._ngControl; | ||
const parent = this._parentFormGroup || this._parentForm; | ||
const newState = control && this.errorStateMatcher(control.control as FormControl, parent); | ||
const errorMatcher = this.errorStateMatcher || this._errorOptions.errorStateMatcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is bad because it loses the error options context. Honestly, I think we should just get rid of ErrorOptions
since there are currently no other options. and make ErrorStateMatcher
a class rather than a function. This was we can easily support overriding via @Input
like we want to do here and the stepper can easily provide its context-dependent matcher
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how making the ErrorStateMatcher
would help, considering that it would still look something like:
class ErrorStateMatcher {
match(): boolean;
}
Otherwise I'll fix it so it doesn't lose the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then instead of making the Input()
take a function you make it take a ErrorStateMatcher
. My other reason for not wanting ErrorOptions
is that things like the stepper which just cares about the matcher then has to worry about all the other options too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, let's just go back to the injection token with a plain function and we can worry about a provider if we really need it.
6d7b7e7
to
2481c3f
Compare
Updated again @mmalerba. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes, I think this will make it a lot cleaner when we go to do the ErrorStateMatcher for the stepper :)
src/lib/input/input.md
Outdated
A global error state matcher can be specified by setting the `MD_ERROR_GLOBAL_OPTIONS` provider. This applies | ||
to all inputs. For convenience, `showOnDirtyErrorStateMatcher` is available in order to globally cause | ||
input errors to show when the input is dirty and invalid. | ||
A global error state matcher can be specified by setting the `ErrorOptions` provider. This applies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ErrorStateMatcher
src/lib/input/input.md
Outdated
|
||
```ts | ||
@NgModule({ | ||
providers: [ | ||
{provide: MD_ERROR_GLOBAL_OPTIONS, useValue: { errorStateMatcher: showOnDirtyErrorStateMatcher }} | ||
{provide: ErrorOptions, useClass: ShowOnDirtyErrorStateMatcher} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ErrorStateMatcher
src/lib/select/select.spec.ts
Outdated
}); | ||
|
||
it('should be able to override the error matching behavior via the injection token', () => { | ||
const errorOptions: ErrorStateMatcher = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
errorStateMatcher
src/lib/core/error/error-options.ts
Outdated
/** Error state matcher that matches when a control is invalid and dirty. */ | ||
@Injectable() | ||
export class ShowOnDirtyErrorStateMatcher implements ErrorStateMatcher { | ||
match(control: NgControl | null, form: FormGroupDirective | NgForm | null): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we call it isErrorSate
? match
is kind of generic and might conflict with some other interface someone is implementing
Addressed the feedback @mmalerba. |
125155d
to
5c6b0a4
Compare
src/lib/core/error/error-options.ts
Outdated
/** Error state matcher that matches when a control is invalid and dirty. */ | ||
@Injectable() | ||
export class ShowOnDirtyErrorStateMatcher implements ErrorStateMatcher { | ||
isErrorSate(control: NgControl | null, form: FormGroupDirective | NgForm | null): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This public API has a typo - isErrorSate
-> isErrorState
. Should be fixed across the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ops, good catch.
* Allows for the `md-select` error behavior to be configured through an `@Input`, as well as globally through the same provider as `md-input-container`. * Simplifies the signature of some of the error option symbols.
16df824
to
32e5de8
Compare
@mmalerba |
In that case let's defer this PR until the form field gets in. |
I think we can resume work on this one now |
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes.
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes.
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes.
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes. Fixes angular#7419.
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes. Fixes angular#7419.
* Allows for the select's error state matcher to be overwritten through an `@Input`. * Switches `MatSelect` over to use the same global provider for its error state as `MatInput`. **Note:** This is a resubmit of angular#6147 that works with our latest setup and excludes a few changes. Fixes angular#7419.
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
md-select
error behavior to be configured through an@Input
, as well as globally through the same provider asmd-input-container
.