Skip to content

Commit 5b8d8c3

Browse files
committed
refactor(material/core): extract error tracking behavior into a class
Moves the error tracking behavior into a separate class that we can use instead of `mixinErrorState`.
1 parent f7d4b65 commit 5b8d8c3

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

src/material/core/common-behaviors/error-state.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,43 @@ export interface HasErrorState {
3737
stateChanges: Subject<void>;
3838
}
3939

40+
/**
41+
* Class that tracks the error state of a component.
42+
* @docs-private
43+
*/
44+
export class _ErrorStateTracker {
45+
/** Whether the tracker is currently in an error state. */
46+
errorState = false;
47+
48+
/** User-defined matcher for the error state. */
49+
matcher: ErrorStateMatcher;
50+
51+
constructor(
52+
private _defaultMatcher: ErrorStateMatcher | null,
53+
public ngControl: NgControl | null,
54+
private _parentFormGroup: FormGroupDirective | null,
55+
private _parentForm: NgForm | null,
56+
private _stateChanges: Subject<void>,
57+
) {}
58+
59+
/** Updates the error state based on the provided error state matcher. */
60+
updateErrorState() {
61+
const oldState = this.errorState;
62+
const parent = this._parentFormGroup || this._parentForm;
63+
const matcher = this.matcher || this._defaultMatcher;
64+
const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null;
65+
// Note: the null check here shouldn't be necessary, but there's an internal
66+
// test that appears to pass an object whose `isErrorState` isn't a function.
67+
const newState =
68+
typeof matcher?.isErrorState === 'function' ? matcher.isErrorState(control, parent) : false;
69+
70+
if (newState !== oldState) {
71+
this.errorState = newState;
72+
this._stateChanges.next();
73+
}
74+
}
75+
}
76+
4077
/**
4178
* Mixin to augment a directive with updateErrorState method.
4279
* For component with `errorState` and need to update `errorState`.
@@ -48,24 +85,41 @@ export function mixinErrorState<T extends Constructor<HasErrorState>>(
4885
base: T,
4986
): CanUpdateErrorStateCtor & T {
5087
return class extends base {
88+
private _tracker: _ErrorStateTracker | undefined;
89+
5190
/** Whether the component is in an error state. */
52-
errorState: boolean = false;
91+
get errorState() {
92+
return this._getTracker().errorState;
93+
}
94+
set errorState(value: boolean) {
95+
this._getTracker().errorState = value;
96+
}
5397

5498
/** An object used to control the error state of the component. */
55-
errorStateMatcher: ErrorStateMatcher;
99+
get errorStateMatcher() {
100+
return this._getTracker().matcher;
101+
}
102+
set errorStateMatcher(value: ErrorStateMatcher) {
103+
this._getTracker().matcher = value;
104+
}
56105

57106
/** Updates the error state based on the provided error state matcher. */
58107
updateErrorState() {
59-
const oldState = this.errorState;
60-
const parent = this._parentFormGroup || this._parentForm;
61-
const matcher = this.errorStateMatcher || this._defaultErrorStateMatcher;
62-
const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null;
63-
const newState = matcher.isErrorState(control, parent);
64-
65-
if (newState !== oldState) {
66-
this.errorState = newState;
67-
this.stateChanges.next();
108+
this._getTracker().updateErrorState();
109+
}
110+
111+
private _getTracker() {
112+
if (!this._tracker) {
113+
this._tracker = new _ErrorStateTracker(
114+
this._defaultErrorStateMatcher,
115+
this.ngControl,
116+
this._parentFormGroup,
117+
this._parentForm,
118+
this.stateChanges,
119+
);
68120
}
121+
122+
return this._tracker;
69123
}
70124

71125
constructor(...args: any[]) {

src/material/core/common-behaviors/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ export {CanDisable, mixinDisabled} from './disabled';
2626
export {CanColor, mixinColor, ThemePalette} from './color';
2727
export {CanDisableRipple, mixinDisableRipple} from './disable-ripple';
2828
export {HasTabIndex, mixinTabIndex} from './tabindex';
29-
export {CanUpdateErrorState, mixinErrorState} from './error-state';
29+
export {CanUpdateErrorState, mixinErrorState, _ErrorStateTracker} from './error-state';
3030
export {HasInitialized, mixinInitialized} from './initialized';

tools/public_api_guard/material/core.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ export class ErrorStateMatcher {
133133
static ɵprov: i0.ɵɵInjectableDeclaration<ErrorStateMatcher>;
134134
}
135135

136+
// @public
137+
export class _ErrorStateTracker {
138+
constructor(_defaultMatcher: ErrorStateMatcher | null, ngControl: NgControl | null, _parentFormGroup: FormGroupDirective | null, _parentForm: NgForm | null, _stateChanges: Subject<void>);
139+
errorState: boolean;
140+
matcher: ErrorStateMatcher;
141+
// (undocumented)
142+
ngControl: NgControl | null;
143+
updateErrorState(): void;
144+
}
145+
136146
// @public
137147
export function _getOptionScrollPosition(optionOffset: number, optionHeight: number, currentScrollPosition: number, panelHeight: number): number;
138148

0 commit comments

Comments
 (0)