Skip to content

Commit

Permalink
fix(buttons): display correct NgModel value inside OnPush component (#…
Browse files Browse the repository at this point in the history
…3000)

Fixes #2980
  • Loading branch information
maxokorokov committed Feb 11, 2019
1 parent 0e9b291 commit 083fb0f
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 12 deletions.
33 changes: 29 additions & 4 deletions src/buttons/checkbox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {TestBed, ComponentFixture, async, fakeAsync, tick} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {createGenericTestComponent} from '../test/common';

import {Component} from '@angular/core';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';

import {NgbButtonsModule} from './buttons.module';
Expand All @@ -11,6 +11,9 @@ import {NgbCheckBox} from './checkbox';
const createTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;

const createOnPushTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponentOnPush) as ComponentFixture<TestComponentOnPush>;

function getLabel(nativeEl: HTMLElement): HTMLElement {
return <HTMLElement>nativeEl.querySelector('label');
}
Expand All @@ -22,8 +25,10 @@ function getInput(nativeEl: HTMLElement): HTMLInputElement {
describe('NgbCheckBox', () => {

beforeEach(() => {
TestBed.configureTestingModule(
{declarations: [TestComponent], imports: [NgbButtonsModule, FormsModule, ReactiveFormsModule]});
TestBed.configureTestingModule({
declarations: [TestComponent, TestComponentOnPush],
imports: [NgbButtonsModule, FormsModule, ReactiveFormsModule]
});
});

describe('bindings', () => {
Expand Down Expand Up @@ -151,10 +156,30 @@ describe('NgbCheckBox', () => {

});

describe('on push', () => {
it('should set initial model value', fakeAsync(() => {
const fixture = createOnPushTestComponent(`
<label ngbButtonLabel>
<input type="checkbox" ngbButton [ngModel]="true">
</label>
`);

fixture.detectChanges();
tick();
fixture.detectChanges();
expect(getInput(fixture.debugElement.nativeElement).checked).toBeTruthy();
expect(getLabel(fixture.debugElement.nativeElement)).toHaveCssClass('active');
}));
});

});

@Component({selector: 'test-cmp', template: ''})
class TestComponent {
disabled;
model;
}

@Component({selector: 'test-cmp-on-push', template: '', changeDetection: ChangeDetectionStrategy.OnPush})
class TestComponentOnPush {
}
7 changes: 5 additions & 2 deletions src/buttons/checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, forwardRef, Input} from '@angular/core';
import {ChangeDetectorRef, Directive, forwardRef, Input} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

import {NgbButtonLabel} from './label';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class NgbCheckBox implements ControlValueAccessor {
}
}

constructor(private _label: NgbButtonLabel) {}
constructor(private _label: NgbButtonLabel, private _cd: ChangeDetectorRef) {}

onInputChange($event) {
const modelToPropagate = $event.target.checked ? this.valueChecked : this.valueUnChecked;
Expand All @@ -75,5 +75,8 @@ export class NgbCheckBox implements ControlValueAccessor {
writeValue(value) {
this.checked = value === this.valueChecked;
this._label.active = this.checked;

// label won't be updated, if it is inside the OnPush component when [ngModel] changes
this._cd.markForCheck();
}
}
31 changes: 27 additions & 4 deletions src/buttons/radio.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {FormControl, FormGroup, FormsModule, NgModel, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser';

Expand Down Expand Up @@ -54,9 +54,12 @@ describe('ngbRadioGroup', () => {
</div>`;

beforeEach(() => {
TestBed.configureTestingModule(
{declarations: [TestComponent], imports: [NgbButtonsModule, FormsModule, ReactiveFormsModule]});
TestBed.configureTestingModule({
declarations: [TestComponent, TestComponentOnPush],
imports: [NgbButtonsModule, FormsModule, ReactiveFormsModule]
});
TestBed.overrideComponent(TestComponent, {set: {template: defaultHtml}});
TestBed.overrideComponent(TestComponentOnPush, {set: {template: defaultHtml}});
});

it('toggles radio inputs based on model changes', async(() => {
Expand Down Expand Up @@ -577,6 +580,20 @@ describe('ngbRadioGroup', () => {
expect(getGroupElement(fixture.nativeElement).getAttribute('role')).toBe('group');
});
});

describe('on push', () => {
it('should set initial model value', fakeAsync(() => {
const fixture = TestBed.createComponent(TestComponentOnPush);
const {values} = fixture.componentInstance;

fixture.detectChanges();
tick();
fixture.detectChanges();
expect(getInput(fixture.nativeElement, 0).value).toEqual(values[0]);
expect(getInput(fixture.nativeElement, 1).value).toEqual(values[1]);
expectRadios(fixture.nativeElement, [1, 0]);
}));
});
});

@Component({selector: 'test-cmp', template: ''})
Expand All @@ -592,3 +609,9 @@ class TestComponent {
groupDisabled = true;
checked: any;
}

@Component({selector: 'test-cmp-on-push', template: '', changeDetection: ChangeDetectionStrategy.OnPush})
class TestComponentOnPush {
model = 'one';
values = ['one', 'two', 'three'];
}
9 changes: 7 additions & 2 deletions src/buttons/radio.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, ElementRef, forwardRef, Input, OnDestroy, Renderer2} from '@angular/core';
import {ChangeDetectorRef, Directive, ElementRef, forwardRef, Input, OnDestroy, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

import {NgbButtonLabel} from './label';
Expand Down Expand Up @@ -127,7 +127,7 @@ export class NgbRadio implements OnDestroy {

constructor(
private _group: NgbRadioGroup, private _label: NgbButtonLabel, private _renderer: Renderer2,
private _element: ElementRef<HTMLInputElement>) {
private _element: ElementRef<HTMLInputElement>, private _cd: ChangeDetectorRef) {
this._group.register(this);
this.updateDisabled();
}
Expand All @@ -137,6 +137,11 @@ export class NgbRadio implements OnDestroy {
onChange() { this._group.onRadioChange(this); }

updateValue(value) {
// label won't be updated, if it is inside the OnPush component when [ngModel] changes
if (this.value !== value) {
this._cd.markForCheck();
}

this._checked = this.value === value;
this._label.active = this._checked;
}
Expand Down

0 comments on commit 083fb0f

Please sign in to comment.