Skip to content

Commit 8d9bbbf

Browse files
devversionandrewseguin
authored andcommitted
fix(checkbox): margin for empty checkboxes incorrectly added (#4730)
* fix(checkbox): margin for empty checkboxes incorrectly added With #2121 the margin will be removed for checkboxes that don't have any label set. A problem is that the Checkbox uses the OnPush change detection strategy and therefore the checkbox is not able to detect any delayed / async label change. This means that checkboxes that set their label from an async binding will not have any margin until the users clicks on the checkbox. Using the `cdkObserveContent` seems to be an elegant approach when using the OnPush strategy. The `:empty` CSS selector would be more elegant but it's very sensitive about whitespaces and therefore it doesn't work properly. Fixes #4720 * Updates
1 parent 436858e commit 8d9bbbf

File tree

4 files changed

+62
-22
lines changed

4 files changed

+62
-22
lines changed

src/lib/checkbox/checkbox.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<label [attr.for]="inputId" class="mat-checkbox-layout" #label>
22
<div class="mat-checkbox-inner-container"
3-
[class.mat-checkbox-inner-container-no-side-margin]="!_hasLabel()">
3+
[class.mat-checkbox-inner-container-no-side-margin]="!checkboxLabel.textContent.trim()">
44
<input #input
55
class="mat-checkbox-input cdk-visually-hidden" type="checkbox"
66
[id]="inputId"
@@ -35,7 +35,7 @@
3535
<div class="mat-checkbox-mixedmark"></div>
3636
</div>
3737
</div>
38-
<span class="mat-checkbox-label" #labelWrapper>
38+
<span class="mat-checkbox-label" #checkboxLabel (cdkObserveContent)="_onLabelTextChange()">
3939
<!-- Add an invisible span so JAWS can read the label -->
4040
<span style="display:none">&nbsp;</span>
4141
<ng-content></ng-content>

src/lib/checkbox/checkbox.spec.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -765,19 +765,57 @@ describe('MdCheckbox', () => {
765765
});
766766

767767
describe('without label', () => {
768-
let checkboxDebugElement: DebugElement;
769-
let checkboxNativeElement: HTMLElement;
768+
let testComponent: CheckboxWithoutLabel;
769+
let checkboxElement: HTMLElement;
770+
let checkboxInnerContainer: HTMLElement;
770771

771-
it('should add a css class to inner-container to remove side margin', () => {
772+
beforeEach(() => {
772773
fixture = TestBed.createComponent(CheckboxWithoutLabel);
774+
775+
const checkboxDebugEl = fixture.debugElement.query(By.directive(MdCheckbox));
776+
777+
testComponent = fixture.componentInstance;
778+
checkboxElement = checkboxDebugEl.nativeElement;
779+
checkboxInnerContainer = checkboxDebugEl
780+
.query(By.css('.mat-checkbox-inner-container')).nativeElement;
781+
});
782+
783+
it('should remove margin for checkbox without a label', () => {
773784
fixture.detectChanges();
774-
checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
775-
checkboxNativeElement = checkboxDebugElement.nativeElement;
776785

777-
let checkboxInnerContainerWithoutMarginCount = checkboxNativeElement
778-
.querySelectorAll('.mat-checkbox-inner-container-no-side-margin').length;
779-
expect(checkboxInnerContainerWithoutMarginCount).toBe(1);
786+
expect(checkboxInnerContainer.classList)
787+
.toContain('mat-checkbox-inner-container-no-side-margin');
780788
});
789+
790+
it('should not remove margin if initial label is set through binding', async(() => {
791+
testComponent.label = 'Some content';
792+
fixture.detectChanges();
793+
794+
expect(checkboxInnerContainer.classList)
795+
.not.toContain('mat-checkbox-inner-container-no-side-margin');
796+
}));
797+
798+
it('should re-add margin if label is added asynchronously', async(() => {
799+
fixture.detectChanges();
800+
801+
expect(checkboxInnerContainer.classList)
802+
.toContain('mat-checkbox-inner-container-no-side-margin');
803+
804+
testComponent.label = 'Some content';
805+
fixture.detectChanges();
806+
807+
// Wait for the MutationObserver to detect the content change and for the cdkObserveContent
808+
// to emit the change event to the checkbox.
809+
setTimeout(() => {
810+
// The MutationObserver from the cdkObserveContent directive detected the content change
811+
// and notified the checkbox component. The checkbox then marks the component as dirty
812+
// by calling `markForCheck()`. This needs to be reflected by the component template then.
813+
fixture.detectChanges();
814+
815+
expect(checkboxInnerContainer.classList)
816+
.not.toContain('mat-checkbox-inner-container-no-side-margin');
817+
}, 1);
818+
}));
781819
});
782820
});
783821

@@ -888,6 +926,8 @@ class CheckboxWithFormControl {
888926

889927
/** Test component without label */
890928
@Component({
891-
template: `<md-checkbox></md-checkbox>`
929+
template: `<md-checkbox>{{ label }}</md-checkbox>`
892930
})
893-
class CheckboxWithoutLabel {}
931+
class CheckboxWithoutLabel {
932+
label: string;
933+
}

src/lib/checkbox/checkbox.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,6 @@ export class MdCheckbox extends _MdCheckboxMixinBase
161161
/** The native `<input type="checkbox"> element */
162162
@ViewChild('input') _inputElement: ElementRef;
163163

164-
@ViewChild('labelWrapper') _labelWrapper: ElementRef;
165-
166-
/** Whether the checkbox has label */
167-
_hasLabel(): boolean {
168-
const labelText = this._labelWrapper.nativeElement.textContent || '';
169-
return !!labelText.trim().length;
170-
}
171-
172164
/** Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. */
173165
@ViewChild(MdRipple) _ripple: MdRipple;
174166

@@ -251,6 +243,14 @@ export class MdCheckbox extends _MdCheckboxMixinBase
251243
return this.disableRipple || this.disabled;
252244
}
253245

246+
/** Method being called whenever the label text changes. */
247+
_onLabelTextChange() {
248+
// This method is getting called whenever the label of the checkbox changes.
249+
// Since the checkbox uses the OnPush strategy we need to notify it about the change
250+
// that has been recognized by the cdkObserveContent directive.
251+
this._changeDetectorRef.markForCheck();
252+
}
253+
254254
/**
255255
* Sets the model value. Implemented as part of ControlValueAccessor.
256256
* @param value Value to be set to the model.

src/lib/checkbox/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {NgModule} from '@angular/core';
22
import {CommonModule} from '@angular/common';
3-
import {MdRippleModule, MdCommonModule, FocusOriginMonitor} from '../core';
3+
import {MdRippleModule, MdCommonModule, FocusOriginMonitor, ObserveContentModule} from '../core';
44
import {MdCheckbox} from './checkbox';
55

66

77
@NgModule({
8-
imports: [CommonModule, MdRippleModule, MdCommonModule],
8+
imports: [CommonModule, MdRippleModule, MdCommonModule, ObserveContentModule],
99
exports: [MdCheckbox, MdCommonModule],
1010
declarations: [MdCheckbox],
1111
providers: [FocusOriginMonitor]

0 commit comments

Comments
 (0)