Skip to content

Commit

Permalink
fix(material/slide-toggle): move required validation into component
Browse files Browse the repository at this point in the history
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 slide toggle and deprecate the old module.
  • Loading branch information
crisbeto committed Dec 5, 2023
1 parent ec86cf8 commit c2c818c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 20 deletions.
16 changes: 7 additions & 9 deletions src/material/slide-toggle/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,22 @@
*/

import {NgModule} from '@angular/core';
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
import {MatCommonModule} from '@angular/material/core';
import {MatSlideToggle} from './slide-toggle';
import {MatSlideToggleRequiredValidator} from './slide-toggle-required-validator';

/** This module is used by both original and MDC-based slide-toggle implementations. */
/**
* @deprecated No longer used, `MatSlideToggle` implements required validation directly.
* @breaking-change 19.0.0
*/
@NgModule({
imports: [MatSlideToggleRequiredValidator],
exports: [MatSlideToggleRequiredValidator],
})
export class _MatSlideToggleRequiredValidatorModule {}

@NgModule({
imports: [
_MatSlideToggleRequiredValidatorModule,
MatCommonModule,
MatRippleModule,
MatSlideToggle,
],
exports: [_MatSlideToggleRequiredValidatorModule, MatSlideToggle, MatCommonModule],
imports: [MatSlideToggle, MatCommonModule],
exports: [MatSlideToggle, MatCommonModule],
})
export class MatSlideToggleModule {}
7 changes: 7 additions & 0 deletions src/material/slide-toggle/slide-toggle-required-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import {Directive, forwardRef, Provider} from '@angular/core';
import {CheckboxRequiredValidator, NG_VALIDATORS} from '@angular/forms';

/**
* @deprecated No longer used, `MatCheckbox` implements required validation directly.
* @breaking-change 19.0.0
*/
export const MAT_SLIDE_TOGGLE_REQUIRED_VALIDATOR: Provider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MatSlideToggleRequiredValidator),
Expand All @@ -22,6 +26,9 @@ export const MAT_SLIDE_TOGGLE_REQUIRED_VALIDATOR: Provider = {
* where the value is always defined.
*
* Required slide-toggle form controls are valid when checked.
*
* @deprecated No longer used, `MatCheckbox` implements required validation directly.
* @breaking-change 19.0.0
*/
@Directive({
selector: `mat-slide-toggle[required][formControlName],
Expand Down
46 changes: 42 additions & 4 deletions src/material/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ import {
Inject,
Input,
numberAttribute,
OnChanges,
OnDestroy,
Optional,
Output,
SimpleChanges,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {FocusMonitor} from '@angular/cdk/a11y';
import {
Expand All @@ -34,7 +43,10 @@ import {
} from './slide-toggle-config';
import {MatRipple} from '@angular/material/core';

/** @docs-private */
/**
* @deprecated Will stop being exported.
* @breaking-change 19.0.0
*/
export const MAT_SLIDE_TOGGLE_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MatSlideToggle),
Expand Down Expand Up @@ -75,13 +87,23 @@ let nextUniqueId = 0;
exportAs: 'matSlideToggle',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [MAT_SLIDE_TOGGLE_VALUE_ACCESSOR],
providers: [
MAT_SLIDE_TOGGLE_VALUE_ACCESSOR,
{
provide: NG_VALIDATORS,
useExisting: MatSlideToggle,
multi: true,
},
],
standalone: true,
imports: [MatRipple],
})
export class MatSlideToggle implements OnDestroy, AfterContentInit, ControlValueAccessor {
export class MatSlideToggle
implements OnDestroy, AfterContentInit, OnChanges, ControlValueAccessor, Validator
{
private _onChange = (_: any) => {};
private _onTouched = () => {};
private _validatorOnChange = () => {};

private _uniqueId: string;
private _checked: boolean = false;
Expand Down Expand Up @@ -211,6 +233,12 @@ export class MatSlideToggle implements OnDestroy, AfterContentInit, ControlValue
});
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['required']) {
this._validatorOnChange();
}
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
}
Expand All @@ -230,6 +258,16 @@ export class MatSlideToggle implements OnDestroy, AfterContentInit, ControlValue
this._onTouched = fn;
}

/** Implemented as a part of Validator. */
validate(control: AbstractControl<boolean>): ValidationErrors | null {
return this.required && control.value !== true ? {'required': true} : null;
}

/** Implemented as a part of Validator. */
registerOnValidatorChange(fn: () => void): void {
this._validatorOnChange = fn;
}

/** Implemented as a part of ControlValueAccessor. */
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
Expand Down
23 changes: 16 additions & 7 deletions tools/public_api_guard/material/slide-toggle.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
```ts

import { AbstractControl } from '@angular/forms';
import { AfterContentInit } from '@angular/core';
import { ChangeDetectorRef } from '@angular/core';
import { CheckboxRequiredValidator } from '@angular/forms';
Expand All @@ -12,28 +13,32 @@ import { ElementRef } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { FocusMonitor } from '@angular/cdk/a11y';
import * as i0 from '@angular/core';
import * as i2 from '@angular/material/core';
import * as i3 from '@angular/material/core';
import { InjectionToken } from '@angular/core';
import { OnChanges } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { Provider } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import { ThemePalette } from '@angular/material/core';
import { Type } from '@angular/core';
import { ValidationErrors } from '@angular/forms';
import { Validator } from '@angular/forms';

// @public
export const MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS: InjectionToken<MatSlideToggleDefaultOptions>;

// @public (undocumented)
// @public @deprecated (undocumented)
export const MAT_SLIDE_TOGGLE_REQUIRED_VALIDATOR: Provider;

// @public
// @public @deprecated (undocumented)
export const MAT_SLIDE_TOGGLE_VALUE_ACCESSOR: {
provide: InjectionToken<readonly ControlValueAccessor[]>;
useExisting: Type<any>;
multi: boolean;
};

// @public (undocumented)
export class MatSlideToggle implements OnDestroy, AfterContentInit, ControlValueAccessor {
export class MatSlideToggle implements OnDestroy, AfterContentInit, OnChanges, ControlValueAccessor, Validator {
constructor(_elementRef: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, tabIndex: string, defaults: MatSlideToggleDefaultOptions, animationMode?: string);
ariaDescribedby: string;
ariaLabel: string | null;
Expand Down Expand Up @@ -78,16 +83,20 @@ export class MatSlideToggle implements OnDestroy, AfterContentInit, ControlValue
// (undocumented)
ngAfterContentInit(): void;
// (undocumented)
ngOnChanges(changes: SimpleChanges): void;
// (undocumented)
ngOnDestroy(): void;
_noopAnimations: boolean;
registerOnChange(fn: any): void;
registerOnTouched(fn: any): void;
registerOnValidatorChange(fn: () => void): void;
required: boolean;
setDisabledState(isDisabled: boolean): void;
_switchElement: ElementRef<HTMLElement>;
tabIndex: number;
toggle(): void;
readonly toggleChange: EventEmitter<void>;
validate(control: AbstractControl<boolean>): ValidationErrors | null;
writeValue(value: any): void;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatSlideToggle, "mat-slide-toggle", ["matSlideToggle"], { "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "color": { "alias": "color"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "name": { "alias": "name"; "required": false; }; "id": { "alias": "id"; "required": false; }; "labelPosition": { "alias": "labelPosition"; "required": false; }; "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "ariaDescribedby": { "alias": "aria-describedby"; "required": false; }; "required": { "alias": "required"; "required": false; }; "checked": { "alias": "checked"; "required": false; }; "hideIcon": { "alias": "hideIcon"; "required": false; }; }, { "change": "change"; "toggleChange": "toggleChange"; }, never, ["*"], true, never>;
Expand Down Expand Up @@ -118,18 +127,18 @@ export class MatSlideToggleModule {
// (undocumented)
static ɵinj: i0.ɵɵInjectorDeclaration<MatSlideToggleModule>;
// (undocumented)
static ɵmod: i0.ɵɵNgModuleDeclaration<MatSlideToggleModule, never, [typeof _MatSlideToggleRequiredValidatorModule, typeof i2.MatCommonModule, typeof i2.MatRippleModule, typeof i3.MatSlideToggle], [typeof _MatSlideToggleRequiredValidatorModule, typeof i3.MatSlideToggle, typeof i2.MatCommonModule]>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MatSlideToggleModule, never, [typeof i2.MatSlideToggle, typeof i3.MatCommonModule], [typeof i2.MatSlideToggle, typeof i3.MatCommonModule]>;
}

// @public
// @public @deprecated
export class MatSlideToggleRequiredValidator extends CheckboxRequiredValidator {
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<MatSlideToggleRequiredValidator, "mat-slide-toggle[required][formControlName], mat-slide-toggle[required][formControl], mat-slide-toggle[required][ngModel]", never, {}, {}, never, never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatSlideToggleRequiredValidator, never>;
}

// @public
// @public @deprecated (undocumented)
export class _MatSlideToggleRequiredValidatorModule {
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<_MatSlideToggleRequiredValidatorModule, never>;
Expand Down

0 comments on commit c2c818c

Please sign in to comment.