Skip to content

Commit e746895

Browse files
devversionmmalerba
authored andcommitted
fix(material-experimental/mdc-slide-toggle): initial checked a… (#17991)
The initial `checked` and `disabled` state of the slide-toggle is not reflected visually. This is because we sync the disabled and checked state after the foundation has been created in the `ngAfterViewInit` lifecycle hook. The foundation then tries to set the classes on the MDC switch element using the adapter. The adapter just updates an object literal that will be passed through `[ngClass]` and relies on Angular to refresh the view. This won't work in Ivy because marking a component as dirty from within the `ngAfterViewInit` lifecycle hook is a noop. See: FW-1354. Just using the native element (like we do in other MDC components) solves this problem and also avoids unnecessary change detections.
1 parent c9856ee commit e746895

File tree

4 files changed

+35
-25
lines changed

4 files changed

+35
-25
lines changed

src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
Disabled Slide Toggle
99
</mat-slide-toggle>
1010

11+
<mat-slide-toggle disabled>
12+
Always disabled (no value binding)
13+
</mat-slide-toggle>
14+
1115
<mat-slide-toggle [disabled]="firstToggle">
1216
Disable Bound
1317
</mat-slide-toggle>

src/material-experimental/mdc-slide-toggle/slide-toggle.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="mdc-form-field"
22
[class.mdc-form-field--align-end]="labelPosition == 'before'">
3-
<div [ngClass]="_classes" #switch>
3+
<div class="mdc-switch" #switch>
44
<div class="mdc-switch__track"></div>
55
<div class="mdc-switch__thumb-underlay">
66
<div class="mat-mdc-slide-toggle-ripple" mat-ripple
@@ -28,9 +28,7 @@
2828
</div>
2929
</div>
3030

31-
<label #label
32-
[for]="inputId"
33-
(click)="$event.stopPropagation()">
31+
<label [for]="inputId" (click)="$event.stopPropagation()">
3432
<ng-content></ng-content>
3533
</label>
3634
</div>

src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('MDC-based MatSlideToggle without forms', () => {
1313
imports: [MatSlideToggleModule, BidiModule],
1414
declarations: [
1515
SlideToggleBasic,
16+
SlideToggleCheckedAndDisabledAttr,
1617
SlideToggleWithTabindexAttr,
1718
SlideToggleWithoutLabel,
1819
SlideToggleProjectedLabel,
@@ -320,6 +321,22 @@ describe('MDC-based MatSlideToggle without forms', () => {
320321
.toBe(5, 'Expected tabIndex property to have been set based on the native attribute');
321322
}));
322323

324+
it('should add the disabled class if disabled through attribute', () => {
325+
const fixture = TestBed.createComponent(SlideToggleCheckedAndDisabledAttr);
326+
fixture.detectChanges();
327+
328+
const switchEl = fixture.nativeElement.querySelector('.mdc-switch');
329+
expect(switchEl.classList).toContain('mdc-switch--disabled');
330+
});
331+
332+
it('should add the checked class if checked through attribute', () => {
333+
const fixture = TestBed.createComponent(SlideToggleCheckedAndDisabledAttr);
334+
fixture.detectChanges();
335+
336+
const switchEl = fixture.nativeElement.querySelector('.mdc-switch');
337+
expect(switchEl.classList).toContain('mdc-switch--checked');
338+
});
339+
323340
it('should remove the tabindex from the host element', fakeAsync(() => {
324341
const fixture = TestBed.createComponent(SlideToggleWithTabindexAttr);
325342

@@ -772,6 +789,11 @@ class SlideToggleWithModel {
772789
isDisabled = false;
773790
}
774791

792+
@Component({
793+
template: `<mat-slide-toggle checked disabled>Label</mat-slide-toggle>`
794+
})
795+
class SlideToggleCheckedAndDisabledAttr {}
796+
775797
@Component({
776798
template: `
777799
<mat-slide-toggle [formControl]="formControl">

src/material-experimental/mdc-slide-toggle/slide-toggle.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,26 +91,15 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
9191
private _checked: boolean = false;
9292
private _foundation: MDCSwitchFoundation;
9393
private _adapter: MDCSwitchAdapter = {
94-
addClass: (className) => {
95-
this._toggleClass(className, true);
96-
},
97-
removeClass: (className) => {
98-
this._toggleClass(className, false);
99-
},
100-
setNativeControlChecked: (checked) => {
101-
this._checked = checked;
102-
},
103-
setNativeControlDisabled: (disabled) => {
104-
this._disabled = disabled;
105-
},
94+
addClass: className => this._switchElement.nativeElement.classList.add(className),
95+
removeClass: className => this._switchElement.nativeElement.classList.remove(className),
96+
setNativeControlChecked: checked => this._checked = checked,
97+
setNativeControlDisabled: disabled => this._disabled = disabled,
10698
};
10799

108100
/** Whether the slide toggle is currently focused. */
109101
_focused: boolean;
110102

111-
/** The set of classes that should be applied to the native input. */
112-
_classes: {[key: string]: boolean} = {'mdc-switch': true};
113-
114103
/** Configuration for the underlying ripple. */
115104
_rippleAnimation: RippleAnimationConfig = {
116105
enterDuration: numbers.DEACTIVATION_TIMEOUT_MS,
@@ -206,6 +195,9 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
206195
/** Reference to the underlying input element. */
207196
@ViewChild('input') _inputElement: ElementRef<HTMLInputElement>;
208197

198+
/** Reference to the MDC switch element. */
199+
@ViewChild('switch') _switchElement: ElementRef<HTMLElement>;
200+
209201
constructor(private _changeDetectorRef: ChangeDetectorRef,
210202
@Attribute('tabindex') tabIndex: string,
211203
@Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS)
@@ -311,12 +303,6 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe
311303
});
312304
}
313305

314-
/** Toggles a class on the switch element. */
315-
private _toggleClass(cssClass: string, active: boolean) {
316-
this._classes[cssClass] = active;
317-
this._changeDetectorRef.markForCheck();
318-
}
319-
320306
static ngAcceptInputType_tabIndex: NumberInput;
321307
static ngAcceptInputType_required: BooleanInput;
322308
static ngAcceptInputType_checked: BooleanInput;

0 commit comments

Comments
 (0)