Skip to content

Commit 0707a77

Browse files
maxokorokovpkozlowski-opensource
authored andcommitted
refactor: use new forms
Closes ng-bootstrap#530
1 parent d9ac911 commit 0707a77

File tree

12 files changed

+578
-380
lines changed

12 files changed

+578
-380
lines changed

demo/src/app/components/timepicker/demos/validation/timepicker-validation.html

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
<p>Illustrates custom validation, you have to select time between 12:00 and 13:59</p>
22

3-
<div class="form-group" [class.has-success]="c.valid" [class.has-danger]="!c.valid">
4-
<ngb-timepicker [(ngModel)]="time" #c="ngForm" [ngFormControl]="lunchControl" required></ngb-timepicker>
3+
<div class="form-group" [class.has-success]="ctrl.valid" [class.has-danger]="!ctrl.valid">
4+
<ngb-timepicker [(ngModel)]="time" [formControl]="ctrl" required></ngb-timepicker>
55
<div class="form-control-feedback">
6-
<div *ngIf="c.valid">Great choice</div>
7-
<div *ngIf="c.errors?.required">Select some time during lunchtime</div>
8-
<div *ngIf="c.errors?.tooLate">Oh no, it's way too late</div>
9-
<div *ngIf="c.errors?.tooEarly">It's a bit too early</div>
10-
<div *ngIf="c.errors?.noMinutesSet">And don't forget to set minutes</div>
6+
<div *ngIf="ctrl.valid">Great choice</div>
7+
<div *ngIf="ctrl.errors?.required">Select some time during lunchtime</div>
8+
<div *ngIf="ctrl.errors?.tooLate">Oh no, it's way too late</div>
9+
<div *ngIf="ctrl.errors?.tooEarly">It's a bit too early</div>
1110
</div>
1211
</div>
1312

Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
11
import {Component} from '@angular/core';
22
import {NGB_TIMEPICKER_DIRECTIVES} from '@ng-bootstrap/ng-bootstrap';
3-
import {Control} from '@angular/common';
4-
5-
const check = (c: boolean) => c ? true : undefined;
3+
import {FormControl, REACTIVE_FORM_DIRECTIVES} from '@angular/forms';
64

75
@Component({
86
selector: 'ngbd-timepicker-validation',
97
template: require('./timepicker-validation.html'),
10-
directives: [NGB_TIMEPICKER_DIRECTIVES]
8+
directives: [NGB_TIMEPICKER_DIRECTIVES, REACTIVE_FORM_DIRECTIVES]
119
})
1210
export class NgbdTimepickerValidation {
1311
time;
1412

15-
lunchControl = new Control('', (control: Control) => {
13+
ctrl = new FormControl('', (control: FormControl) => {
1614
const value = control.value;
1715

1816
if (!value) {
1917
return null;
2018
}
2119

22-
const result = {};
23-
2420
if (value.hour < 12) {
25-
result['tooEarly'] = true;
21+
return {tooEarly: true};
2622
}
2723
if (value.hour > 13) {
28-
result['tooLate'] = true;
29-
}
30-
if (isNaN(value.minute)) {
31-
result['noMinutesSet'] = true;
24+
return {tooLate: true};
3225
}
3326

34-
return Object.keys(result).length > 0 ? result : null;
27+
return null;
3528
});
3629
}

demo/src/main.ts

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {Angulartics2GoogleAnalytics} from 'angulartics2/src/providers/angulartic
77
import {APP_ROUTER_PROVIDERS} from './app/app.routes';
88
import {AppComponent} from './app/app.component';
99
import {Angulartics} from './angulartics2.workaround';
10+
import {disableDeprecatedForms, provideForms} from '@angular/forms';
1011

1112
// depending on the env mode, enable prod mode or add debugging modules
1213
if (process.env.ENV === 'build') {
@@ -18,6 +19,10 @@ bootstrap(AppComponent, [
1819
APP_ROUTER_PROVIDERS,
1920
{provide: LocationStrategy, useClass: HashLocationStrategy},
2021

22+
// forms
23+
disableDeprecatedForms(),
24+
provideForms(),
25+
2126
// google analytics dependencies
2227
{provide: Angulartics2, useClass: Angulartics},
2328
Angulartics2GoogleAnalytics

demo/src/vendor.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '@angular/platform-browser-dynamic';
44
import '@angular/core';
55
import '@angular/common';
66
import '@angular/router';
7+
import '@angular/forms';
78

89
// RxJS
910
import 'rxjs';

karma-test-shim.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ System.config({
1616
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
1717
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
1818
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
19+
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
1920
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
2021
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
2122
'rxjs': {defaultExtension: 'js'}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
},
2929
"devDependencies": {
3030
"@angular/compiler": "2.0.0-rc.4",
31+
"@angular/forms": "0.2.0",
3132
"@angular/http": "2.0.0-rc.4",
3233
"@angular/platform-browser": "2.0.0-rc.4",
3334
"@angular/platform-browser-dynamic": "2.0.0-rc.4",

src/buttons/radio.spec.ts

+108-67
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import {inject, async} from '@angular/core/testing';
1+
import {inject, async, addProviders} from '@angular/core/testing';
22
import {TestComponentBuilder} from '@angular/compiler/testing';
33
import {Component} from '@angular/core';
44

55
import {NGB_RADIO_DIRECTIVES} from './radio';
6-
import {Control, Validators, FormBuilder} from '@angular/common';
7-
6+
import {
7+
Validators,
8+
provideForms,
9+
disableDeprecatedForms,
10+
FormControl,
11+
REACTIVE_FORM_DIRECTIVES,
12+
FormGroup
13+
} from '@angular/forms';
814

915
function expectRadios(element: HTMLElement, states: number[]) {
1016
const labels = element.querySelectorAll('label');
@@ -30,6 +36,9 @@ function getInput(nativeEl: HTMLElement, idx: number): HTMLInputElement {
3036
}
3137

3238
describe('ngbRadioGroup', () => {
39+
40+
beforeEach(() => { addProviders([disableDeprecatedForms(), provideForms()]); });
41+
3342
const defaultHtml = `<div [(ngModel)]="model" ngbRadioGroup>
3443
<label class="btn">
3544
<input type="radio" name="radio" [value]="values[0]"/> {{ values[0] }}
@@ -39,6 +48,7 @@ describe('ngbRadioGroup', () => {
3948
</label>
4049
</div>`;
4150

51+
// TODO: remove 'whenStable' once 'core/testing' is fixed
4252
it('toggles radio inputs based on model changes', async(inject([TestComponentBuilder], (tcb) => {
4353
tcb.overrideTemplate(TestComponent, defaultHtml).createAsync(TestComponent).then((fixture) => {
4454

@@ -53,22 +63,34 @@ describe('ngbRadioGroup', () => {
5363
// checking null
5464
fixture.componentInstance.model = null;
5565
fixture.detectChanges();
56-
expectRadios(fixture.nativeElement, [0, 0]);
57-
58-
// checking first radio
59-
fixture.componentInstance.model = values[0];
60-
fixture.detectChanges();
61-
expectRadios(fixture.nativeElement, [1, 0]);
62-
63-
// checking second radio
64-
fixture.componentInstance.model = values[1];
65-
fixture.detectChanges();
66-
expectRadios(fixture.nativeElement, [0, 1]);
67-
68-
// checking non-matching value
69-
fixture.componentInstance.model = values[3];
70-
fixture.detectChanges();
71-
expectRadios(fixture.nativeElement, [0, 0]);
66+
fixture.whenStable().then(() => {
67+
fixture.detectChanges();
68+
expectRadios(fixture.nativeElement, [0, 0]);
69+
70+
// checking first radio
71+
fixture.componentInstance.model = values[0];
72+
fixture.detectChanges();
73+
fixture.whenStable().then(() => {
74+
fixture.detectChanges();
75+
expectRadios(fixture.nativeElement, [1, 0]);
76+
77+
// checking second radio
78+
fixture.componentInstance.model = values[1];
79+
fixture.detectChanges();
80+
fixture.whenStable().then(() => {
81+
fixture.detectChanges();
82+
expectRadios(fixture.nativeElement, [0, 1]);
83+
84+
// checking non-matching value
85+
fixture.componentInstance.model = values[3];
86+
fixture.detectChanges();
87+
fixture.whenStable().then(() => {
88+
fixture.detectChanges();
89+
expectRadios(fixture.nativeElement, [0, 0]);
90+
});
91+
});
92+
});
93+
});
7294
});
7395
})));
7496

@@ -92,6 +114,7 @@ describe('ngbRadioGroup', () => {
92114
});
93115
})));
94116

117+
// TODO: remove 'whenStable' once 'core/testing' is fixed
95118
it('can be used with objects as values', async(inject([TestComponentBuilder], (tcb) => {
96119
tcb.overrideTemplate(TestComponent, defaultHtml).createAsync(TestComponent).then((fixture) => {
97120

@@ -109,16 +132,20 @@ describe('ngbRadioGroup', () => {
109132
// checking model -> radio input
110133
fixture.componentInstance.model = one;
111134
fixture.detectChanges();
112-
expectRadios(fixture.nativeElement, [1, 0]);
113-
114-
// checking radio click -> model
115-
getInput(fixture.nativeElement, 1).click();
116-
fixture.detectChanges();
117-
expectRadios(fixture.nativeElement, [0, 1]);
118-
expect(fixture.componentInstance.model).toBe(two);
135+
fixture.whenStable().then(() => {
136+
fixture.detectChanges();
137+
expectRadios(fixture.nativeElement, [1, 0]);
138+
139+
// checking radio click -> model
140+
getInput(fixture.nativeElement, 1).click();
141+
fixture.detectChanges();
142+
expectRadios(fixture.nativeElement, [0, 1]);
143+
expect(fixture.componentInstance.model).toBe(two);
144+
});
119145
});
120146
})));
121147

148+
// TODO: remove 'whenStable' once 'core/testing' is fixed
122149
it('updates radio input values dynamically', async(inject([TestComponentBuilder], (tcb) => {
123150
tcb.overrideTemplate(TestComponent, defaultHtml).createAsync(TestComponent).then((fixture) => {
124151

@@ -127,26 +154,30 @@ describe('ngbRadioGroup', () => {
127154
// checking first radio
128155
fixture.componentInstance.model = values[0];
129156
fixture.detectChanges();
130-
expectRadios(fixture.nativeElement, [1, 0]);
131-
expect(fixture.componentInstance.model).toEqual(values[0]);
132-
133-
// updating first radio value -> expecting none selected
134-
let initialValue = values[0];
135-
values[0] = 'ten';
136-
fixture.detectChanges();
137-
expectRadios(fixture.nativeElement, [0, 0]);
138-
expect(getInput(fixture.nativeElement, 0).value).toEqual('ten');
139-
expect(fixture.componentInstance.model).toEqual(initialValue);
140-
141-
// updating values back -> expecting initial state
142-
values[0] = initialValue;
143-
fixture.detectChanges();
144-
expectRadios(fixture.nativeElement, [1, 0]);
145-
expect(getInput(fixture.nativeElement, 0).value).toEqual(values[0]);
146-
expect(fixture.componentInstance.model).toEqual(values[0]);
157+
fixture.whenStable().then(() => {
158+
fixture.detectChanges();
159+
expectRadios(fixture.nativeElement, [1, 0]);
160+
expect(fixture.componentInstance.model).toEqual(values[0]);
161+
162+
// updating first radio value -> expecting none selected
163+
let initialValue = values[0];
164+
values[0] = 'ten';
165+
fixture.detectChanges();
166+
expectRadios(fixture.nativeElement, [0, 0]);
167+
expect(getInput(fixture.nativeElement, 0).value).toEqual('ten');
168+
expect(fixture.componentInstance.model).toEqual(initialValue);
169+
170+
// updating values back -> expecting initial state
171+
values[0] = initialValue;
172+
fixture.detectChanges();
173+
expectRadios(fixture.nativeElement, [1, 0]);
174+
expect(getInput(fixture.nativeElement, 0).value).toEqual(values[0]);
175+
expect(fixture.componentInstance.model).toEqual(values[0]);
176+
});
147177
});
148178
})));
149179

180+
// TODO: remove 'whenStable' once 'core/testing' is fixed
150181
it('can be used with ngFor', async(inject([TestComponentBuilder], (tcb) => {
151182

152183
const forHtml = `<div [(ngModel)]="model" ngbRadioGroup>
@@ -164,10 +195,14 @@ describe('ngbRadioGroup', () => {
164195

165196
fixture.componentInstance.model = values[1];
166197
fixture.detectChanges();
167-
expectRadios(fixture.nativeElement, [0, 1, 0]);
198+
fixture.whenStable().then(() => {
199+
fixture.detectChanges();
200+
expectRadios(fixture.nativeElement, [0, 1, 0]);
201+
});
168202
});
169203
})));
170204

205+
// TODO: remove 'whenStable' once 'core/testing' is fixed
171206
it('cleans up the model when radio inputs are added / removed', async(inject([TestComponentBuilder], (tcb) => {
172207

173208
const ifHtml = `<div [(ngModel)]="model" ngbRadioGroup>
@@ -200,17 +235,21 @@ describe('ngbRadioGroup', () => {
200235
// hiding/showing selected radio -> expecting model to unchange, but none selected
201236
fixture.componentInstance.model = values[1];
202237
fixture.detectChanges();
203-
expectRadios(fixture.nativeElement, [0, 1]);
238+
fixture.whenStable().then(() => {
239+
fixture.detectChanges();
240+
expectRadios(fixture.nativeElement, [0, 1]);
204241

205-
fixture.componentInstance.shown = false;
206-
fixture.detectChanges();
207-
expectRadios(fixture.nativeElement, [0]);
208-
expect(fixture.componentInstance.model).toEqual(values[1]);
242+
fixture.componentInstance.shown = false;
243+
fixture.detectChanges();
244+
expectRadios(fixture.nativeElement, [0]);
245+
expect(fixture.componentInstance.model).toEqual(values[1]);
246+
247+
fixture.componentInstance.shown = true;
248+
fixture.detectChanges();
249+
expectRadios(fixture.nativeElement, [0, 1]);
250+
expect(fixture.componentInstance.model).toEqual(values[1]);
251+
});
209252

210-
fixture.componentInstance.shown = true;
211-
fixture.detectChanges();
212-
expectRadios(fixture.nativeElement, [0, 1]);
213-
expect(fixture.componentInstance.model).toEqual(values[1]);
214253
});
215254
})));
216255

@@ -225,10 +264,11 @@ describe('ngbRadioGroup', () => {
225264
});
226265
})));
227266

267+
// TODO: remove 'whenStable' once 'core/testing' is fixed
228268
it('should work with template-driven form validation', async(inject([TestComponentBuilder], (tcb) => {
229269
const html = `
230270
<form>
231-
<div ngbRadioGroup [(ngModel)]="model" required>
271+
<div ngbRadioGroup [(ngModel)]="model" name="control" required>
232272
<label class="btn">
233273
<input type="radio" value="foo"/>
234274
</label>
@@ -237,20 +277,23 @@ describe('ngbRadioGroup', () => {
237277

238278
tcb.overrideTemplate(TestComponent, html).createAsync(TestComponent).then((fixture) => {
239279
fixture.detectChanges();
240-
expect(getGroupElement(fixture.nativeElement)).toHaveCssClass('ng-invalid');
241-
expect(getGroupElement(fixture.nativeElement)).not.toHaveCssClass('ng-valid');
242-
243-
getInput(fixture.nativeElement, 0).click();
244-
fixture.detectChanges();
245-
expect(getGroupElement(fixture.nativeElement)).toHaveCssClass('ng-valid');
246-
expect(getGroupElement(fixture.nativeElement)).not.toHaveCssClass('ng-invalid');
280+
fixture.whenStable().then(() => {
281+
fixture.detectChanges();
282+
expect(getGroupElement(fixture.nativeElement)).toHaveCssClass('ng-invalid');
283+
expect(getGroupElement(fixture.nativeElement)).not.toHaveCssClass('ng-valid');
284+
285+
getInput(fixture.nativeElement, 0).click();
286+
fixture.detectChanges();
287+
expect(getGroupElement(fixture.nativeElement)).toHaveCssClass('ng-valid');
288+
expect(getGroupElement(fixture.nativeElement)).not.toHaveCssClass('ng-invalid');
289+
});
247290
});
248291
})));
249292

250293
it('should work with model-driven form validation', async(inject([TestComponentBuilder], (tcb) => {
251294
const html = `
252-
<form [ngFormModel]="form">
253-
<div ngbRadioGroup ngControl="control">
295+
<form [formGroup]="form">
296+
<div ngbRadioGroup formControlName="control">
254297
<label class="btn">
255298
<input type="radio" value="foo"/>
256299
</label>
@@ -270,13 +313,11 @@ describe('ngbRadioGroup', () => {
270313
})));
271314
});
272315

273-
@Component({selector: 'test-cmp', directives: [NGB_RADIO_DIRECTIVES], template: ''})
316+
@Component({selector: 'test-cmp', directives: [NGB_RADIO_DIRECTIVES, REACTIVE_FORM_DIRECTIVES], template: ''})
274317
class TestComponent {
275-
form = this._builder.group({control: new Control('', Validators.required)});
318+
form = new FormGroup({control: new FormControl('', Validators.required)});
276319

277320
model;
278321
values = ['one', 'two', 'three'];
279322
shown = true;
280-
281-
constructor(private _builder: FormBuilder) {}
282323
}

src/buttons/radio.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Directive, forwardRef, Optional, Input, Renderer, ElementRef, OnDestroy} from '@angular/core';
2-
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/common';
2+
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
33

44
const NGB_RADIO_VALUE_ACCESSOR = {
55
provide: NG_VALUE_ACCESSOR,

0 commit comments

Comments
 (0)