Skip to content

Commit b2b8bdc

Browse files
IlyaSurmayvalorkin
authored andcommitted
fix(buttons): fix radio btns for reactive forms, add radio reactive demo (#3384)
fixes #2581
1 parent 1cd6f94 commit b2b8bdc

File tree

12 files changed

+196
-44
lines changed

12 files changed

+196
-44
lines changed

demo/src/app/components/+buttons/buttons-section.list.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DemoButtonsBasicComponent } from './demos/basic/basic';
22
import { DemoButtonsCheckboxComponent } from './demos/checkbox/checkbox';
33
import { DemoButtonsRadioComponent } from './demos/radio/radio';
4+
import { DemoButtonsCheckboxReactiveFormsComponent } from './demos/checkbox-reactiveforms/checkbox-reactiveforms';
45
import { DemoButtonsRadioReactiveFormsComponent } from './demos/radio-reactiveforms/radio-reactiveforms';
56
import { DemoButtonsDisabledComponent } from './demos/disabled/disabled';
67

@@ -41,18 +42,30 @@ export const demoComponentContent: ContentSection[] = [
4142
html: require('!!raw-loader?lang=markup!./demos/checkbox/checkbox.html'),
4243
outlet: DemoButtonsCheckboxComponent
4344
},
45+
{
46+
title: 'Checkbox with Reactive Forms',
47+
anchor: 'checkbox-reactiveforms"',
48+
description: `<p>Checkbox buttons with ReactiveForms</p>`,
49+
component: require('!!raw-loader?lang=typescript!./demos/checkbox-reactiveforms/checkbox-reactiveforms.ts'),
50+
html: require('!!raw-loader?lang=markup!./demos/checkbox-reactiveforms/checkbox-reactiveforms.html'),
51+
outlet: DemoButtonsCheckboxReactiveFormsComponent
52+
},
4453
{
4554
title: 'Radio & Uncheckable Radio',
4655
anchor: 'radio-button',
47-
description: `<p>Radio buttons with checked/unchecked states</p>`,
56+
description: `<p>Radio buttons with checked/unchecked states. Group can be created in two ways: using
57+
<code>btnRadioGroup</code> directive or using the same <code>ngModel</code> binding with several buttons (works only for
58+
template driven forms). Check the demo below for more info.</p>`,
4859
component: require('!!raw-loader?lang=typescript!./demos/radio/radio.ts'),
4960
html: require('!!raw-loader?lang=markup!./demos/radio/radio.html'),
5061
outlet: DemoButtonsRadioComponent
5162
},
5263
{
5364
title: 'Radio with Reactive Forms',
54-
anchor: 'radio-reactiveforms"',
55-
description: `<p>Checkbox buttons with ReactiveForms</p>`,
65+
anchor: 'radio-reactiveforms',
66+
description: `<p>Radio buttons with ReactiveForms. Example below shows how to use radio buttons with reactive
67+
forms. Please be aware that for reactive forms it's required to use <code>btnRadioGroup</code> directive along with
68+
<code>btnRadio</code>'s</p>`,
5669
component: require('!!raw-loader?lang=typescript!./demos/radio-reactiveforms/radio-reactiveforms.ts'),
5770
html: require('!!raw-loader?lang=markup!./demos/radio-reactiveforms/radio-reactiveforms.html'),
5871
outlet: DemoButtonsRadioReactiveFormsComponent
@@ -80,6 +93,11 @@ export const demoComponentContent: ContentSection[] = [
8093
title: 'ButtonRadioDirective',
8194
anchor: 'button-radio-directive',
8295
outlet: NgApiDocComponent
96+
},
97+
{
98+
title: 'ButtonRadioGroupDirective',
99+
anchor: 'button-radio-group-directive',
100+
outlet: NgApiDocComponent
83101
}
84102
]
85103
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<pre class="card card-block card-header">{{myForm.value | json}}</pre>
2+
<form [formGroup]="myForm">
3+
<div class="btn-group">
4+
<label class="btn btn-primary" [class.active]="myForm.value.left"
5+
btnCheckbox formControlName="left">Left</label>
6+
7+
<label class="btn btn-primary" [class.active]="myForm.value.middle"
8+
btnCheckbox formControlName="middle">Middle</label>
9+
10+
<label class="btn btn-primary" [class.active]="myForm.value.right"
11+
btnCheckbox formControlName="right">Right</label>
12+
</div>
13+
</form>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { FormBuilder, FormGroup } from '@angular/forms';
3+
4+
@Component({
5+
selector: 'demo-buttons-checkbox-reactiveforms',
6+
templateUrl: './checkbox-reactiveforms.html'
7+
})
8+
export class DemoButtonsCheckboxReactiveFormsComponent implements OnInit {
9+
myForm: FormGroup;
10+
11+
constructor(private formBuilder: FormBuilder) {}
12+
13+
ngOnInit() {
14+
this.myForm = this.formBuilder.group({
15+
left: false,
16+
middle: true,
17+
right: false
18+
});
19+
}
20+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { DemoButtonsBasicComponent } from './basic/basic';
22
import { DemoButtonsCheckboxComponent } from './checkbox/checkbox';
33
import { DemoButtonsRadioComponent } from './radio/radio';
4-
import { DemoButtonsRadioReactiveFormsComponent } from './radio-reactiveforms/radio-reactiveforms';
4+
import { DemoButtonsCheckboxReactiveFormsComponent } from './checkbox-reactiveforms/checkbox-reactiveforms';
55
import { DemoButtonsDisabledComponent } from './disabled/disabled';
6+
import { DemoButtonsRadioReactiveFormsComponent } from './radio-reactiveforms/radio-reactiveforms';
67

78
export const DEMO_COMPONENTS = [
89
DemoButtonsBasicComponent,
910
DemoButtonsCheckboxComponent,
1011
DemoButtonsRadioComponent,
12+
DemoButtonsCheckboxReactiveFormsComponent,
1113
DemoButtonsRadioReactiveFormsComponent,
1214
DemoButtonsDisabledComponent
1315
];
Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
<pre class="card card-block card-header">{{myForm.value | json}}</pre>
2-
<form [formGroup]="myForm">
3-
<div class="btn-group">
4-
<label class="btn btn-primary" [class.active]="myForm.value.left"
5-
btnCheckbox formControlName="left">Left</label>
6-
7-
<label class="btn btn-primary" [class.active]="myForm.value.middle"
8-
btnCheckbox formControlName="middle">Middle</label>
9-
10-
<label class="btn btn-primary" [class.active]="myForm.value.right"
11-
btnCheckbox formControlName="right">Right</label>
1+
<pre class="card card-block card-header">{{ myForm.value | json }}</pre>
2+
<form [formGroup]="myForm" class="form-inline">
3+
<div class="form-group">
4+
<div class="btn-group" btnRadioGroup formControlName="radio">
5+
<label btnRadio="A" class="btn btn-primary">A</label>
6+
<label btnRadio="B" class="btn btn-primary">B</label>
7+
<label btnRadio="C" class="btn btn-primary">C</label>
8+
</div>
129
</div>
1310
</form>

demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, OnInit } from '@angular/core';
2-
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
2+
import { FormBuilder, FormGroup } from '@angular/forms';
33

44
@Component({
55
selector: 'demo-buttons-radio-reactiveforms',
@@ -12,9 +12,7 @@ export class DemoButtonsRadioReactiveFormsComponent implements OnInit {
1212

1313
ngOnInit() {
1414
this.myForm = this.formBuilder.group({
15-
left: false,
16-
middle: true,
17-
right: false
15+
radio: 'C'
1816
});
1917
}
2018
}

demo/src/app/components/+buttons/demos/radio/radio.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
<label class="btn btn-primary" [(ngModel)]="radioModel" btnRadio="Middle">Middle</label>
55
<label class="btn btn-primary" [(ngModel)]="radioModel" btnRadio="Right">Right</label>
66
</div>
7-
<div class="btn-group">
8-
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Left" uncheckable>Left</label>
9-
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Middle" uncheckable>Middle</label>
10-
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Right" uncheckable>Right</label>
7+
<div class="btn-group" btnRadioGroup [(ngModel)]="radioModel">
8+
<label class="btn btn-success" btnRadio="Left">Left</label>
9+
<label class="btn btn-success" btnRadio="Middle">Middle</label>
10+
<label class="btn btn-success" btnRadio="Right">Right</label>
11+
</div>
12+
<div class="btn-group" btnRadioGroup [(ngModel)]="radioModel">
13+
<label class="btn btn-info" btnRadio="Left" uncheckable>Left</label>
14+
<label class="btn btn-info" btnRadio="Middle" uncheckable>Middle</label>
15+
<label class="btn btn-info" btnRadio="Right" uncheckable>Right</label>
1116
</div>

demo/src/ng-api-doc.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,16 @@ export const ngdoc: any = {
323323
"properties": [],
324324
"methods": []
325325
},
326+
"ButtonRadioGroupDirective": {
327+
"fileName": "src/buttons/button-radio-group.directive.ts",
328+
"className": "ButtonRadioGroupDirective",
329+
"description": "<p>A group of radio buttons.\nA value of a selected button is bound to a variable specified via ngModel.</p>\n",
330+
"selector": "[btnRadioGroup]",
331+
"inputs": [],
332+
"outputs": [],
333+
"properties": [],
334+
"methods": []
335+
},
326336
"ButtonRadioDirective": {
327337
"fileName": "src/buttons/button-radio.directive.ts",
328338
"className": "ButtonRadioDirective",
@@ -334,6 +344,11 @@ export const ngdoc: any = {
334344
"type": "any",
335345
"description": "<p>Radio button value, will be set to <code>ngModel</code> </p>\n"
336346
},
347+
{
348+
"name": "disabled",
349+
"type": "boolean",
350+
"description": "<p>If <code>true</code> — radio button is disabled </p>\n"
351+
},
337352
{
338353
"name": "uncheckable",
339354
"type": "boolean",
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// tslint:disable:no-use-before-declare
2+
import { ChangeDetectorRef, Directive, ElementRef, forwardRef, Input } from '@angular/core';
3+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
4+
5+
export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
6+
provide: NG_VALUE_ACCESSOR,
7+
useExisting: forwardRef(() => ButtonRadioGroupDirective),
8+
multi: true
9+
};
10+
11+
/**
12+
* A group of radio buttons.
13+
* A value of a selected button is bound to a variable specified via ngModel.
14+
*/
15+
@Directive({
16+
selector: '[btnRadioGroup]',
17+
providers: [RADIO_CONTROL_VALUE_ACCESSOR]
18+
})
19+
export class ButtonRadioGroupDirective implements ControlValueAccessor {
20+
onChange: any = Function.prototype;
21+
onTouched: any = Function.prototype;
22+
23+
get value(): any {
24+
return this._value;
25+
}
26+
set value(value: any) {
27+
this._value = value;
28+
}
29+
30+
private _value: any;
31+
32+
constructor(private el: ElementRef, private cdr: ChangeDetectorRef) {}
33+
34+
writeValue(value: any): void {
35+
this._value = value;
36+
this.cdr.markForCheck();
37+
}
38+
39+
registerOnChange(fn: any): void {
40+
this.onChange = fn;
41+
}
42+
43+
registerOnTouched(fn: any): void {
44+
this.onTouched = fn;
45+
}
46+
}

src/buttons/button-radio.directive.ts

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// tslint:disable:no-use-before-declare
22
import {
3-
ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding,
4-
HostListener, Input, OnInit
3+
ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, OnInit,
4+
Optional, Renderer2
55
} from '@angular/core';
66
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
7+
import { ButtonRadioGroupDirective } from './button-radio-group.directive';
78

89
export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
910
provide: NG_VALUE_ACCESSOR,
@@ -22,41 +23,56 @@ export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
2223
export class ButtonRadioDirective implements ControlValueAccessor, OnInit {
2324
onChange: any = Function.prototype;
2425
onTouched: any = Function.prototype;
26+
private _value: any;
27+
private _disabled: boolean;
2528

2629
/** Radio button value, will be set to `ngModel` */
2730
@Input() btnRadio: any;
2831
/** If `true` — radio button can be unchecked */
2932
@Input() uncheckable: boolean;
3033
/** Current value of radio component or group */
31-
@Input() value: any;
34+
@Input() get value(): any {
35+
return this.group ? this.group.value : this._value;
36+
}
37+
38+
set value(value: any) {
39+
if (this.group) {
40+
this.group.value = value;
41+
42+
return;
43+
}
44+
this._value = value;
45+
}
46+
/** If `true` — radio button is disabled */
47+
@Input() get disabled(): boolean {
48+
return this._disabled;
49+
}
50+
51+
set disabled(disabled: boolean) {
52+
this._disabled = disabled;
53+
this.setDisabledState(disabled);
54+
}
3255

3356
@HostBinding('class.active')
3457
get isActive(): boolean {
3558
return this.btnRadio === this.value;
3659
}
3760

38-
constructor(private el: ElementRef, private cdr: ChangeDetectorRef) {
39-
}
61+
constructor(
62+
private el: ElementRef,
63+
private cdr: ChangeDetectorRef,
64+
@Optional() private group: ButtonRadioGroupDirective,
65+
private renderer: Renderer2
66+
) {}
4067

4168
@HostListener('click')
4269
onClick(): void {
43-
if (this.el.nativeElement.attributes.disabled) {
44-
return;
45-
}
46-
47-
if (this.uncheckable && this.btnRadio === this.value) {
48-
this.value = undefined;
49-
this.onTouched();
50-
this.onChange(this.value);
51-
70+
if (this.el.nativeElement.attributes.disabled || !this.uncheckable && this.btnRadio === this.value) {
5271
return;
5372
}
5473

55-
if (this.btnRadio !== this.value) {
56-
this.value = this.btnRadio;
57-
this.onTouched();
58-
this.onChange(this.value);
59-
}
74+
this.value = this.uncheckable && this.btnRadio === this.value ? undefined : this.btnRadio;
75+
this._onChange(this.value);
6076
}
6177

6278
ngOnInit(): void {
@@ -67,6 +83,17 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit {
6783
this.onTouched();
6884
}
6985

86+
_onChange(value: any): void {
87+
if (this.group) {
88+
this.group.onTouched();
89+
this.group.onChange(value);
90+
91+
return;
92+
}
93+
this.onTouched();
94+
this.onChange(value);
95+
}
96+
7097
// ControlValueAccessor
7198
// model -> view
7299
writeValue(value: any): void {
@@ -81,4 +108,13 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit {
81108
registerOnTouched(fn: any): void {
82109
this.onTouched = fn;
83110
}
111+
112+
setDisabledState(disabled: boolean): void {
113+
if (disabled) {
114+
this.renderer.setAttribute(this.el.nativeElement, 'disabled', 'disabled');
115+
116+
return;
117+
}
118+
this.renderer.removeAttribute(this.el.nativeElement, 'disabled');
119+
}
84120
}

0 commit comments

Comments
 (0)