Skip to content

Commit ec86cf8

Browse files
committed
fix(material/checkbox): move required validation into component
Currently required validation is implemented as a separate directive. This is problematic, because with standalone users would have to add two imports. These changes move the required validation into the checkbox and deprecate the old module.
1 parent 8dc96a7 commit ec86cf8

File tree

5 files changed

+75
-20
lines changed

5 files changed

+75
-20
lines changed

src/material/checkbox/checkbox-required-validator.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@
99
import {Directive, forwardRef, Provider} from '@angular/core';
1010
import {CheckboxRequiredValidator, NG_VALIDATORS} from '@angular/forms';
1111

12+
/**
13+
* @deprecated No longer used, `MatCheckbox` implements required validation directly.
14+
* @breaking-change 19.0.0
15+
*/
1216
export const MAT_CHECKBOX_REQUIRED_VALIDATOR: Provider = {
1317
provide: NG_VALIDATORS,
1418
useExisting: forwardRef(() => MatCheckboxRequiredValidator),
1519
multi: true,
1620
};
1721

18-
// TODO(crisbeto): this should be deprecated and moved into `MatCheckbox`.
19-
2022
/**
2123
* Validator for Material checkbox's required attribute in template-driven checkbox.
2224
* Current CheckboxRequiredValidator only work with `input type=checkbox` and does not
2325
* work with `mat-checkbox`.
26+
*
27+
* @deprecated No longer used, `MatCheckbox` implements required validation directly.
28+
* @breaking-change 19.0.0
2429
*/
2530
@Directive({
2631
selector: `mat-checkbox[required][formControlName],

src/material/checkbox/checkbox.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
MatCheckbox,
1111
MatCheckboxChange,
1212
MatCheckboxModule,
13-
MatCheckboxRequiredValidator,
1413
} from './index';
1514

1615
describe('MDC-based MatCheckbox', () => {
@@ -1100,7 +1099,7 @@ class SingleCheckbox {
11001099
template: `<mat-checkbox [required]="isRequired" [(ngModel)]="isGood"
11011100
[disabled]="isDisabled">Be good</mat-checkbox>`,
11021101
standalone: true,
1103-
imports: [MatCheckbox, MatCheckboxRequiredValidator, FormsModule],
1102+
imports: [MatCheckbox, FormsModule],
11041103
})
11051104
class CheckboxWithNgModel {
11061105
isGood: boolean = false;

src/material/checkbox/checkbox.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ import {
2020
Input,
2121
NgZone,
2222
numberAttribute,
23+
OnChanges,
2324
Optional,
2425
Output,
26+
SimpleChanges,
2527
ViewChild,
2628
ViewEncapsulation,
2729
} from '@angular/core';
28-
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
30+
import {
31+
AbstractControl,
32+
ControlValueAccessor,
33+
NG_VALIDATORS,
34+
NG_VALUE_ACCESSOR,
35+
ValidationErrors,
36+
Validator,
37+
} from '@angular/forms';
2938
import {MatRipple} from '@angular/material/core';
3039
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
3140
import {FocusableOption} from '@angular/cdk/a11y';
@@ -50,6 +59,10 @@ export enum TransitionCheckState {
5059
Indeterminate,
5160
}
5261

62+
/**
63+
* @deprecated Will stop being exported.
64+
* @breaking-change 19.0.0
65+
*/
5366
export const MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR: any = {
5467
provide: NG_VALUE_ACCESSOR,
5568
useExisting: forwardRef(() => MatCheckbox),
@@ -87,14 +100,23 @@ const defaults = MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY();
87100
'[class.mat-mdc-checkbox-checked]': 'checked',
88101
'[class]': 'color ? "mat-" + color : "mat-accent"',
89102
},
90-
providers: [MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR],
103+
providers: [
104+
MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR,
105+
{
106+
provide: NG_VALIDATORS,
107+
useExisting: MatCheckbox,
108+
multi: true,
109+
},
110+
],
91111
exportAs: 'matCheckbox',
92112
encapsulation: ViewEncapsulation.None,
93113
changeDetection: ChangeDetectionStrategy.OnPush,
94114
standalone: true,
95115
imports: [MatRipple],
96116
})
97-
export class MatCheckbox implements AfterViewInit, ControlValueAccessor, FocusableOption {
117+
export class MatCheckbox
118+
implements AfterViewInit, OnChanges, ControlValueAccessor, Validator, FocusableOption
119+
{
98120
/** Focuses the checkbox. */
99121
focus() {
100122
this._inputElement.nativeElement.focus();
@@ -197,10 +219,9 @@ export class MatCheckbox implements AfterViewInit, ControlValueAccessor, Focusab
197219
_onTouched: () => any = () => {};
198220

199221
private _currentAnimationClass: string = '';
200-
201222
private _currentCheckState: TransitionCheckState = TransitionCheckState.Init;
202-
203223
private _controlValueAccessorChangeFn: (value: any) => void = () => {};
224+
private _validatorChangeFn = () => {};
204225

205226
constructor(
206227
public _elementRef: ElementRef<HTMLElement>,
@@ -216,6 +237,12 @@ export class MatCheckbox implements AfterViewInit, ControlValueAccessor, Focusab
216237
this.id = this._uniqueId = `mat-mdc-checkbox-${++nextUniqueId}`;
217238
}
218239

240+
ngOnChanges(changes: SimpleChanges) {
241+
if (changes['required']) {
242+
this._validatorChangeFn();
243+
}
244+
}
245+
219246
ngAfterViewInit() {
220247
this._syncIndeterminate(this._indeterminate);
221248
}
@@ -309,6 +336,16 @@ export class MatCheckbox implements AfterViewInit, ControlValueAccessor, Focusab
309336
this.disabled = isDisabled;
310337
}
311338

339+
// Implemented as a part of Validator.
340+
validate(control: AbstractControl<boolean>): ValidationErrors | null {
341+
return this.required && control.value !== true ? {'required': true} : null;
342+
}
343+
344+
// Implemented as a part of Validator.
345+
registerOnValidatorChange(fn: () => void): void {
346+
this._validatorChangeFn = fn;
347+
}
348+
312349
private _transitionCheckState(newState: TransitionCheckState) {
313350
let oldState = this._currentCheckState;
314351
let element = this._getAnimationTargetElement();

src/material/checkbox/module.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10-
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
10+
import {MatCommonModule} from '@angular/material/core';
1111
import {MatCheckbox} from './checkbox';
1212
import {MatCheckboxRequiredValidator} from './checkbox-required-validator';
1313

14-
/** This module is used by both original and MDC-based checkbox implementations. */
14+
/**
15+
* @deprecated No longer used, `MatCheckbox` implements required validation directly.
16+
* @breaking-change 19.0.0
17+
*/
1518
@NgModule({
1619
imports: [MatCheckboxRequiredValidator],
1720
exports: [MatCheckboxRequiredValidator],
1821
})
1922
export class _MatCheckboxRequiredValidatorModule {}
2023

2124
@NgModule({
22-
imports: [MatCommonModule, MatRippleModule, _MatCheckboxRequiredValidatorModule, MatCheckbox],
23-
exports: [MatCheckbox, MatCommonModule, _MatCheckboxRequiredValidatorModule],
25+
imports: [MatCheckbox, MatCommonModule],
26+
exports: [MatCheckbox, MatCommonModule],
2427
})
2528
export class MatCheckboxModule {}

tools/public_api_guard/material/checkbox.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
```ts
66

7+
import { AbstractControl } from '@angular/forms';
78
import { AfterViewInit } from '@angular/core';
89
import { ChangeDetectorRef } from '@angular/core';
910
import { CheckboxRequiredValidator } from '@angular/forms';
@@ -12,14 +13,18 @@ import { ElementRef } from '@angular/core';
1213
import { EventEmitter } from '@angular/core';
1314
import { FocusableOption } from '@angular/cdk/a11y';
1415
import * as i0 from '@angular/core';
15-
import * as i2 from '@angular/material/core';
16+
import * as i3 from '@angular/material/core';
1617
import { InjectionToken } from '@angular/core';
1718
import { MatRipple } from '@angular/material/core';
1819
import { NgZone } from '@angular/core';
20+
import { OnChanges } from '@angular/core';
1921
import { Provider } from '@angular/core';
22+
import { SimpleChanges } from '@angular/core';
2023
import { ThemePalette } from '@angular/material/core';
24+
import { ValidationErrors } from '@angular/forms';
25+
import { Validator } from '@angular/forms';
2126

22-
// @public (undocumented)
27+
// @public @deprecated (undocumented)
2328
export const MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR: any;
2429

2530
// @public
@@ -28,11 +33,11 @@ export const MAT_CHECKBOX_DEFAULT_OPTIONS: InjectionToken<MatCheckboxDefaultOpti
2833
// @public
2934
export function MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY(): MatCheckboxDefaultOptions;
3035

31-
// @public (undocumented)
36+
// @public @deprecated (undocumented)
3237
export const MAT_CHECKBOX_REQUIRED_VALIDATOR: Provider;
3338

3439
// @public (undocumented)
35-
export class MatCheckbox implements AfterViewInit, ControlValueAccessor, FocusableOption {
40+
export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccessor, Validator, FocusableOption {
3641
constructor(_elementRef: ElementRef<HTMLElement>, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, tabIndex: string, _animationMode?: string | undefined, _options?: MatCheckboxDefaultOptions | undefined);
3742
protected _animationClasses: {
3843
uncheckedToChecked: string;
@@ -87,6 +92,8 @@ export class MatCheckbox implements AfterViewInit, ControlValueAccessor, Focusab
8792
// (undocumented)
8893
ngAfterViewInit(): void;
8994
// (undocumented)
95+
ngOnChanges(changes: SimpleChanges): void;
96+
// (undocumented)
9097
_onBlur(): void;
9198
// (undocumented)
9299
_onInputClick(): void;
@@ -101,13 +108,17 @@ export class MatCheckbox implements AfterViewInit, ControlValueAccessor, Focusab
101108
registerOnChange(fn: (value: any) => void): void;
102109
// (undocumented)
103110
registerOnTouched(fn: any): void;
111+
// (undocumented)
112+
registerOnValidatorChange(fn: () => void): void;
104113
required: boolean;
105114
// @deprecated
106115
ripple: MatRipple;
107116
// (undocumented)
108117
setDisabledState(isDisabled: boolean): void;
109118
tabIndex: number;
110119
toggle(): void;
120+
// (undocumented)
121+
validate(control: AbstractControl<boolean>): ValidationErrors | null;
111122
value: string;
112123
// (undocumented)
113124
writeValue(value: any): void;
@@ -139,18 +150,18 @@ export class MatCheckboxModule {
139150
// (undocumented)
140151
static ɵinj: i0.ɵɵInjectorDeclaration<MatCheckboxModule>;
141152
// (undocumented)
142-
static ɵmod: i0.ɵɵNgModuleDeclaration<MatCheckboxModule, never, [typeof i2.MatCommonModule, typeof i2.MatRippleModule, typeof _MatCheckboxRequiredValidatorModule, typeof i3.MatCheckbox], [typeof i3.MatCheckbox, typeof i2.MatCommonModule, typeof _MatCheckboxRequiredValidatorModule]>;
153+
static ɵmod: i0.ɵɵNgModuleDeclaration<MatCheckboxModule, never, [typeof i2.MatCheckbox, typeof i3.MatCommonModule], [typeof i2.MatCheckbox, typeof i3.MatCommonModule]>;
143154
}
144155

145-
// @public
156+
// @public @deprecated
146157
export class MatCheckboxRequiredValidator extends CheckboxRequiredValidator {
147158
// (undocumented)
148159
static ɵdir: i0.ɵɵDirectiveDeclaration<MatCheckboxRequiredValidator, "mat-checkbox[required][formControlName], mat-checkbox[required][formControl], mat-checkbox[required][ngModel]", never, {}, {}, never, never, true, never>;
149160
// (undocumented)
150161
static ɵfac: i0.ɵɵFactoryDeclaration<MatCheckboxRequiredValidator, never>;
151162
}
152163

153-
// @public
164+
// @public @deprecated (undocumented)
154165
export class _MatCheckboxRequiredValidatorModule {
155166
// (undocumented)
156167
static ɵfac: i0.ɵɵFactoryDeclaration<_MatCheckboxRequiredValidatorModule, never>;

0 commit comments

Comments
 (0)