diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4be44b08e4..5ea22e526e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# Ignite UI for Angular Change Log
All notable changes for each version of this project will be documented in this file.
+## 6.0.4
+- **igxRadioGroup** directive introduced. It allows better control over its child `igxRadio` components and support template-driven and reactive forms.
## 6.0.3
- **igxGrid** exposing the `filteredSortedData` method publicly - returns the grid data with current filtering and sorting applied.
diff --git a/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.spec.ts
new file mode 100644
index 00000000000..050266ada05
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.spec.ts
@@ -0,0 +1,269 @@
+import { Component, ViewChild } from '@angular/core';
+import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { IgxRadioModule, IgxRadioGroupDirective } from './radio-group.directive';
+import { FormsModule, ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms';
+
+describe('IgxRadioGroupDirective', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ RadioGroupComponent,
+ RadioGroupWithModelComponent,
+ RadioGroupReactiveFormsComponent
+ ],
+ imports: [
+ IgxRadioModule,
+ FormsModule,
+ ReactiveFormsModule
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('Properly initialize the radio group buttons\' properties.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.radioButtons).toBeDefined();
+ expect(radioInstance.radioButtons.length).toEqual(3);
+
+ const allRequiredButtons = radioInstance.radioButtons.filter((btn) => btn.required);
+ expect(allRequiredButtons.length).toEqual(radioInstance.radioButtons.length);
+
+ const allButtonsWithGroupName = radioInstance.radioButtons.filter((btn) => btn.name === radioInstance.name);
+ expect(allButtonsWithGroupName.length).toEqual(radioInstance.radioButtons.length);
+
+ const allButtonsWithGroupLabelPos = radioInstance.radioButtons.filter((btn) => btn.labelPosition === radioInstance.labelPosition);
+ expect(allButtonsWithGroupLabelPos.length).toEqual(radioInstance.radioButtons.length);
+
+ const buttonWithGroupValue = radioInstance.radioButtons.find((btn) => btn.value === radioInstance.value);
+ expect(buttonWithGroupValue).toBeDefined();
+ expect(buttonWithGroupValue).toEqual(radioInstance.selected);
+ }));
+
+ it('Setting radioGroup\'s properties should affect all radio buttons.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.radioButtons).toBeDefined();
+
+ // name
+ radioInstance.name = 'newGroupName';
+ fixture.detectChanges();
+
+ const allButtonsWithNewName = radioInstance.radioButtons.filter((btn) => btn.name === 'newGroupName');
+ expect(allButtonsWithNewName.length).toEqual(radioInstance.radioButtons.length);
+
+ // required
+ radioInstance.required = true;
+ fixture.detectChanges();
+
+ const allRequiredButtons = radioInstance.radioButtons.filter((btn) => btn.required);
+ expect(allRequiredButtons.length).toEqual(radioInstance.radioButtons.length);
+
+ // labelPosition
+ radioInstance.labelPosition = 'after';
+ fixture.detectChanges();
+
+ const allAfterButtons = radioInstance.radioButtons.filter((btn) => btn.labelPosition === 'after');
+ expect(allAfterButtons.length).toEqual(radioInstance.radioButtons.length);
+
+ // disabled
+ radioInstance.disabled = true;
+ fixture.detectChanges();
+
+ const allDisabledButtons = radioInstance.radioButtons.filter((btn) => btn.disabled);
+ expect(allDisabledButtons.length).toEqual(radioInstance.radioButtons.length);
+ }));
+
+ it('Set value should change selected property and emit change event.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.value).toBeDefined();
+ expect(radioInstance.value).toEqual('Baz');
+
+ expect(radioInstance.selected).toBeDefined();
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.last);
+
+ spyOn(radioInstance.change, 'emit');
+
+ radioInstance.value = 'Foo';
+ fixture.detectChanges();
+
+ expect(radioInstance.value).toEqual('Foo');
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.first);
+ expect(radioInstance.change.emit).toHaveBeenCalled();
+ }));
+
+ it('Set selected property should change value and emit change event.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.value).toBeDefined();
+ expect(radioInstance.value).toEqual('Baz');
+
+ expect(radioInstance.selected).toBeDefined();
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.last);
+
+ spyOn(radioInstance.change, 'emit');
+
+ radioInstance.selected = radioInstance.radioButtons.first;
+ fixture.detectChanges();
+
+ expect(radioInstance.value).toEqual('Foo');
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.first);
+ expect(radioInstance.change.emit).toHaveBeenCalled();
+ }));
+
+ it('Clicking on a radio button should update the model.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupWithModelComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ radioInstance.radioButtons.first.nativeLabel.nativeElement.click();
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.value).toEqual('Winter');
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.first);
+ }));
+
+ it('Updating the model should select a radio button.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupWithModelComponent);
+ const radioInstance = fixture.componentInstance.radioGroup;
+
+ fixture.detectChanges();
+ tick();
+
+ fixture.componentInstance.personBob.favoriteSeason = 'Winter';
+ fixture.detectChanges();
+ tick();
+
+ expect(radioInstance.value).toEqual('Winter');
+ expect(radioInstance.selected).toEqual(radioInstance.radioButtons.first);
+ }));
+
+ it('Properly update the model when radio group is hosted in Reactive forms.', fakeAsync(() => {
+ const fixture = TestBed.createComponent(RadioGroupReactiveFormsComponent);
+
+ fixture.detectChanges();
+ tick();
+
+ expect(fixture.componentInstance.personForm).toBeDefined();
+ expect(fixture.componentInstance.model).toBeDefined();
+ expect(fixture.componentInstance.newModel).toBeUndefined();
+
+ fixture.componentInstance.personForm.patchValue({ favoriteSeason: fixture.componentInstance.seasons[0] });
+ fixture.componentInstance.updateModel();
+ fixture.detectChanges();
+ tick();
+
+ expect(fixture.componentInstance.newModel).toBeDefined();
+ expect(fixture.componentInstance.newModel.name).toEqual(fixture.componentInstance.model.name);
+ expect(fixture.componentInstance.newModel.favoriteSeason).toEqual(fixture.componentInstance.seasons[0]);
+ }));
+});
+
+@Component({
+ template: `
+
+ {{item}}
+
+
+`
+})
+class RadioGroupComponent {
+ @ViewChild('radioGroup', { read: IgxRadioGroupDirective }) public radioGroup: IgxRadioGroupDirective;
+}
+
+class Person {
+ name: string;
+ favoriteSeason: string;
+}
+
+@Component({
+ template: `
+
+ {{item}}
+
+
+`
+})
+class RadioGroupWithModelComponent {
+ seasons = [
+ 'Winter',
+ 'Spring',
+ 'Summer',
+ 'Autumn',
+ ];
+
+ @ViewChild('radioGroupSeasons', { read: IgxRadioGroupDirective }) public radioGroup: IgxRadioGroupDirective;
+
+ personBob: Person = { name: 'Bob', favoriteSeason: 'Summer' };
+}
+
+@Component({
+ template: `
+
+`
+})
+class RadioGroupReactiveFormsComponent {
+ seasons = [
+ 'Winter',
+ 'Spring',
+ 'Summer',
+ 'Autumn',
+ ];
+
+ newModel: Person;
+ model: Person = { name: 'Kirk', favoriteSeason: this.seasons[1] };
+ personForm: FormGroup;
+
+ constructor(private _formBuilder: FormBuilder) {
+ this._createForm();
+ }
+
+ updateModel() {
+ const formModel = this.personForm.value;
+
+ this.newModel = {
+ name: formModel.name as string,
+ favoriteSeason: formModel.favoriteSeason as string
+ };
+ }
+
+ private _createForm() {
+ // create form
+ this.personForm = this._formBuilder.group({
+ name: '',
+ favoriteSeason: ''
+ });
+
+ // simulate model loading from service
+ this.personForm.setValue({
+ name: this.model.name,
+ favoriteSeason: this.model.favoriteSeason
+ });
+ }
+}
diff --git a/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.ts b/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.ts
new file mode 100644
index 00000000000..7225f124a94
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.ts
@@ -0,0 +1,350 @@
+import { Directive, NgModule, Input, QueryList, Output, EventEmitter, AfterContentInit, ContentChildren } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { IgxRadioComponent, RadioLabelPosition, IChangeRadioEventArgs } from '../../radio/radio.component';
+import { IgxRippleModule } from '../ripple/ripple.directive';
+
+const noop = () => { };
+let nextId = 0;
+
+/**
+ * **Ignite UI for Angular Radio Group** -
+ * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio_group.html)
+ *
+ * The Ignite UI Radio Group allows the user to select a single option from an available set of options that are listed side by side.
+ *
+ * Example:
+ * ```html
+ *
+ *
+ * {{item}}
+ *
+ *
+ * ```
+ */
+@Directive({
+ selector: 'igx-radio-group, [igxRadioGroup]',
+ providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: IgxRadioGroupDirective, multi: true }]
+})
+export class IgxRadioGroupDirective implements AfterContentInit, ControlValueAccessor {
+ /**
+ * Returns reference to the child radio buttons.
+ * ```typescript
+ * let radioButtons = this.radioGroup.radioButtons;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @ContentChildren(IgxRadioComponent) public radioButtons: QueryList;
+
+ /**
+ * Sets/gets the `value` attribute.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let value = this.radioGroup.value;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get value(): any { return this._value; }
+ set value(newValue: any) {
+ if (this._value !== newValue) {
+ this._value = newValue;
+ this._selectRadioButton();
+ }
+ }
+
+ /**
+ * Sets/gets the `name` attribute of the radio group component. All child radio buttons inherits this name.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let name = this.radioGroup.name;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get name(): string { return this._name; }
+ set name(newValue: string) {
+ if (this._name !== newValue) {
+ this._name = newValue;
+ this._setRadioButtonNames();
+ }
+ }
+
+ /**
+ * Sets/gets whether the radio group is required.
+ * If not set, `required` will have value `false`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let isRequired = this.radioGroup.required;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get required(): boolean { return this._required; }
+ set required(newValue: boolean) {
+ if (this._required !== newValue) {
+ this._required = newValue;
+ this._setRadioButtonsRequired();
+ }
+ }
+
+ /**
+ * An @Input property that allows you to disable the radio group. By default it's false.
+ * ```html
+ *
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get disabled(): boolean { return this._disabled; }
+ set disabled(newValue: boolean) {
+ if (this._disabled !== newValue) {
+ this._disabled = newValue;
+ this._disableRadioButtons();
+ }
+ }
+
+ /**
+ * Sets/gets the position of the `label` in the child radio buttons.
+ * If not set, `labelPosition` will have value `"after"`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let labelPosition = this.radioGroup.labelPosition;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get labelPosition(): RadioLabelPosition | string { return this._labelPosition; }
+ set labelPosition(newValue: RadioLabelPosition | string) {
+ if (this._labelPosition !== newValue) {
+ this._labelPosition = newValue === RadioLabelPosition.BEFORE ? RadioLabelPosition.BEFORE : RadioLabelPosition.AFTER;
+ this._setRadioButtonLabelPosition();
+ }
+ }
+
+ /**
+ * Sets/gets the selected child radio button.
+ * ```typescript
+ * let selectedButton = this.radioGroup.selected;
+ * this.radioGroup.selected = selectedButton;
+ * ```
+ * @memberof IgxRadioGroupDirective
+ */
+ @Input()
+ get selected() { return this._selected; }
+ set selected(selected: IgxRadioComponent | null) {
+ if (this._selected !== selected) {
+ this._selected = selected;
+ this.value = selected ? selected.value : null;
+ }
+ }
+
+ /**
+ * An event that is emitted after the radio group `value` is changed.
+ * Provides references to the selected `IgxRadioComponent` and the `value` property as event arguments.
+ * @memberof IgxRadioGroupDirective
+ */
+ @Output()
+ readonly change: EventEmitter = new EventEmitter();
+
+ /**
+ *@hidden
+ */
+ private _onChangeCallback: (_: any) => void = noop;
+ /**
+ *@hidden
+ */
+ private _name = `igx-radio-group-${nextId++}`;
+ /**
+ *@hidden
+ */
+ private _value: any = null;
+ /**
+ *@hidden
+ */
+ private _selected: IgxRadioComponent | null = null;
+ /**
+ *@hidden
+ */
+ private _isInitialized = false;
+ /**
+ *@hidden
+ */
+ private _labelPosition: RadioLabelPosition | string = 'after';
+ /**
+ *@hidden
+ */
+ private _disabled = false;
+ /**
+ *@hidden
+ */
+ private _required = false;
+
+ ngAfterContentInit() {
+ // The initial value can possibly be set by NgModel and it is possible that
+ // the OnInit of the NgModel occurs after the OnInit of this class.
+ this._isInitialized = true;
+
+ setTimeout(() => { this._initRadioButtons(); });
+ }
+
+ /**
+ * Checks whether the provided value is consistent to the current radio button.
+ * If it is, the checked attribute will have value `true` and selected property will contain the selected `IgxRadioComponent`.
+ * ```typescript
+ * this.radioGroup.writeValue('radioButtonValue');
+ * ```
+ */
+ public writeValue(value: any) {
+ this.value = value;
+ }
+
+ /**
+ *@hidden
+ */
+ public registerOnChange(fn: (_: any) => void) { this._onChangeCallback = fn; }
+
+ /**
+ *@hidden
+ */
+ public registerOnTouched(fn: () => void) {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.registerOnTouched(fn);
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _initRadioButtons() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.name = this._name;
+ button.labelPosition = this._labelPosition;
+ button.disabled = this._disabled;
+ button.required = this._required;
+
+ if (this._value && button.value === this._value) {
+ button.checked = true;
+ this._selected = button;
+ }
+
+ button.change.subscribe((ev) => this._selectedRadioButtonChanged(ev));
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _selectedRadioButtonChanged(args: IChangeRadioEventArgs) {
+ if (this._selected !== args.radio) {
+ if (this._selected) {
+ this._selected.checked = false;
+ }
+ this._selected = args.radio;
+ }
+
+ this._value = args.value;
+
+ if (this._isInitialized) {
+ this.change.emit(args);
+ this._onChangeCallback(this.value);
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _setRadioButtonNames() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.name = this._name;
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _selectRadioButton() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ if (!this._value) {
+ // no value - uncheck all radio buttons
+ if (button.checked) {
+ button.checked = false;
+ }
+ } else {
+ if (this._value === button.value) {
+ // selected button
+ if (this._selected !== button) {
+ this._selected = button;
+ }
+
+ if (!button.checked) {
+ button.select();
+ }
+ } else {
+ // non-selected button
+ if (button.checked) {
+ button.checked = false;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _setRadioButtonLabelPosition() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.labelPosition = this._labelPosition;
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _disableRadioButtons() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.disabled = this._disabled;
+ });
+ }
+ }
+
+ /**
+ *@hidden
+ */
+ private _setRadioButtonsRequired() {
+ if (this.radioButtons) {
+ this.radioButtons.forEach((button) => {
+ button.required = this._required;
+ });
+ }
+ }
+}
+
+/**
+ *The IgxRadioModule provides the {@link IgxRadioGroupDirective} and {@link IgxRadioComponent} inside your application.
+ */
+@NgModule({
+ declarations: [IgxRadioGroupDirective, IgxRadioComponent],
+ exports: [IgxRadioGroupDirective, IgxRadioComponent],
+ imports: [IgxRippleModule]
+})
+export class IgxRadioModule { }
diff --git a/projects/igniteui-angular/src/lib/radio/radio.component.ts b/projects/igniteui-angular/src/lib/radio/radio.component.ts
index 624747051a9..86868c21985 100644
--- a/projects/igniteui-angular/src/lib/radio/radio.component.ts
+++ b/projects/igniteui-angular/src/lib/radio/radio.component.ts
@@ -1,15 +1,12 @@
import {
Component,
EventEmitter,
- forwardRef,
HostBinding,
Input,
- NgModule,
Output,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
-import { IgxRippleModule } from '../directives/ripple/ripple.directive';
export interface IChangeRadioEventArgs {
value: any;
@@ -43,64 +40,250 @@ const noop = () => { };
})
export class IgxRadioComponent implements ControlValueAccessor {
+ /**
+ * Returns reference to native radio element.
+ * ```typescript
+ * let radioElement = this.radio.nativeRadio;
+ * ```
+ * @memberof IgxSwitchComponent
+ */
@ViewChild('radio') public nativeRadio;
+ /**
+ * Returns reference to native label element.
+ * ```typescript
+ * let labelElement = this.radio.nativeLabel;
+ * ```
+ * @memberof IgxSwitchComponent
+ */
@ViewChild('nativeLabel') public nativeLabel;
+ /**
+ * Returns reference to the label placeholder element.
+ * ```typescript
+ * let labelPlaceholder = this.radio.placeholderLabel;
+ * ```
+ * @memberof IgxSwitchComponent
+ */
@ViewChild('placeholderLabel') public placeholderLabel;
- /** ID of the component */
+ /**
+ * Sets/gets the `id` of the radio component.
+ * If not set, the `id` of the first radio component will be `"igx-radio-0"`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let radioId = this.radio.id;
+ * ```
+ * @memberof IgxRadioComponent
+ */
@HostBinding('attr.id')
@Input() public id = `igx-radio-${nextId++}`;
+ /**
+ * Sets/gets the id of the `label` element in the radio component.
+ * If not set, the id of the `label` in the first radio component will be `"igx-radio-0-label"`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let labelId = this.radio.labelId;
+ * ```
+ * @memberof IgxRadioComponent
+ */
@Input() public labelId = `${this.id}-label`;
+ /**
+ * Sets/gets the position of the `label` in the radio component.
+ * If not set, `labelPosition` will have value `"after"`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let labelPosition = this.radio.labelPosition;
+ * ```
+ * @memberof IgxRadioComponent
+ */
@Input() public labelPosition: RadioLabelPosition | string = 'after';
+ /**
+ * Sets/gets the `value` attribute.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let value = this.radio.value;
+ * ```
+ * @memberof IgxRadioComponent
+ */
@Input() public value: any;
- @Input() public name: string;
- @Input() public tabindex: number = null;
- @Input() public disableRipple = false;
- @Input() public required = false;
-
- @Input('aria-labelledby')
+/**
+ * Sets/gets the `name` attribute of the radio component.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let name = this.radio.name;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input() public name: string;
+/**
+ * Sets the value of the `tabindex` attribute.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let tabIndex = this.radio.tabindex;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input() public tabindex: number = null;
+/**
+ * Enables/disables the ripple effect on the radio button..
+ * If not set, the `disableRipple` will have value `false`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let isDisabledRipple = this.radio.disableRipple;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input() public disableRipple = false;
+/**
+ * Sets/gets whether the radio button is required.
+ * If not set, `required` will have value `false`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let isRequired = this.radio.required;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input() public required = false;
+/**
+ * Sets/gets the `aria-labelledby` attribute of the radio component.
+ * If not set, the `aria-labelledby` will be equal to the value of `labelId` attribute.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let ariaLabelledBy = this.radio.ariaLabelledBy;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input('aria-labelledby')
public ariaLabelledBy = this.labelId;
-
- @Input('aria-label')
+/**
+ * Sets/gets the `aria-label` attribute of the radio component.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let ariaLabel = this.radio.ariaLabel;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@Input('aria-label')
public ariaLabel: string | null = null;
-
- @Output()
+/**
+ * An event that is emitted after the radio `value` is changed.
+ * Provides references to the `IgxRadioComponent` and the `value` property as event arguments.
+ * @memberof IgxRadioComponent
+ */
+@Output()
readonly change: EventEmitter = new EventEmitter();
-
- @HostBinding('class.igx-radio')
+/**
+ * Returns the class of the radio component.
+ * ```typescript
+ * let radioClass = this.radio.cssClass;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@HostBinding('class.igx-radio')
public cssClass = 'igx-radio';
-
- @HostBinding('class.igx-radio--checked')
+/**
+ * Sets/gets the `checked` attribute.
+ * Default value is `false`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let isChecked = this.radio.checked;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@HostBinding('class.igx-radio--checked')
@Input() public checked = false;
-
- @HostBinding('class.igx-radio--disabled')
+/**
+ * Sets/gets the `disabled` attribute.
+ * Default value is `false`.
+ * ```html
+ *
+ * ```
+ * ```typescript
+ * let isDisabled = this.radio.disabled;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@HostBinding('class.igx-radio--disabled')
@Input() public disabled = false;
-
- @HostBinding('class.igx-radio--focused')
+/**
+ * Sets/gets whether the radio component is on focus.
+ * Default value is `false`.
+ * ```typescript
+ * this.radio.focus = true;
+ * ```
+ * ```typescript
+ * let isFocused = this.radio.focused;
+ * ```
+ * @memberof IgxRadioComponent
+ */
+@HostBinding('class.igx-radio--focused')
public focused = false;
-
- public inputId = `${this.id}-input`;
+/**
+ *@hidden
+ */
+public inputId = `${this.id}-input`;
+ /**
+ *@hidden
+ */
protected _value: any = null;
constructor() { }
-
- private _onTouchedCallback: () => void = noop;
- private _onChangeCallback: (_: any) => void = noop;
-
- public _onRadioChange(event) {
+/**
+ *@hidden
+ */
+private _onTouchedCallback: () => void = noop;
+/**
+ *@hidden
+ */
+private _onChangeCallback: (_: any) => void = noop;
+/**
+ *@hidden
+ */
+public _onRadioChange(event) {
event.stopPropagation();
}
-
- public _onRadioClick(event) {
+/**
+ *@hidden
+ */
+public _onRadioClick(event) {
event.stopPropagation();
this.select();
}
-
- public _onLabelClick() {
+/**
+ *@hidden
+ */
+public _onLabelClick() {
this.select();
}
-
- public select() {
+/**
+ * Selects the current radio button.
+ * ```typescript
+ * this.radio.select();
+ * ```
+ * @memberof IgxRadioComponent
+ */
+public select() {
if (this.disabled) {
return;
}
@@ -110,13 +293,21 @@ export class IgxRadioComponent implements ControlValueAccessor {
this.change.emit({ value: this.value, radio: this });
this._onChangeCallback(this.value);
}
-
- public writeValue(value: any) {
+/**
+ * Checks whether the provided value is consistent to the current radio button.
+ * If it is, the checked attribute will have value `true`;
+ * ```typescript
+ * this.radio.writeValue('radioButtonValue');
+ * ```
+ */
+public writeValue(value: any) {
this._value = value;
this.checked = (this._value === this.value);
}
-
- public get labelClass(): string {
+/**
+ *@hidden
+ */
+public get labelClass(): string {
switch (this.labelPosition) {
case RadioLabelPosition.BEFORE:
return `${this.cssClass}__label--before`;
@@ -125,23 +316,25 @@ export class IgxRadioComponent implements ControlValueAccessor {
return `${this.cssClass}__label`;
}
}
-
- public onFocus(event) {
+/**
+ *@hidden
+ */
+public onFocus(event) {
this.focused = true;
}
-
- public onBlur(event) {
+/**
+ *@hidden
+ */
+public onBlur(event) {
this.focused = false;
this._onTouchedCallback();
}
-
- public registerOnChange(fn: (_: any) => void) { this._onChangeCallback = fn; }
- public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; }
+/**
+ *@hidden
+ */
+public registerOnChange(fn: (_: any) => void) { this._onChangeCallback = fn; }
+/**
+ *@hidden
+ */
+public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; }
}
-
-@NgModule({
- declarations: [IgxRadioComponent],
- exports: [IgxRadioComponent],
- imports: [IgxRippleModule]
-})
-export class IgxRadioModule { }
diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts
index 68a92590360..eef4acab225 100644
--- a/projects/igniteui-angular/src/public_api.ts
+++ b/projects/igniteui-angular/src/public_api.ts
@@ -19,6 +19,7 @@ export * from './lib/directives/focus/focus.directive';
export * from './lib/directives/for-of/for_of.directive';
export * from './lib/directives/layout/layout.directive';
export * from './lib/directives/mask/mask.directive';
+export * from './lib/directives/radio/radio-group.directive';
export * from './lib/directives/ripple/ripple.directive';
export * from './lib/directives/text-highlight/text-highlight.directive';
export * from './lib/directives/text-selection/text-selection.directive';
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 375e88ed8ea..c78140d35a8 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -140,6 +140,11 @@ export class AppComponent implements OnInit {
icon: 'poll',
name: 'Progress Indicators'
},
+ {
+ link: '/radio',
+ icon: 'pol',
+ name: 'Radio Group'
+ },
{
link: '/slider',
icon: 'linear_scale',
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 2ca40c1b1fd..3dcdecc6e9e 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,7 +1,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
-import { FormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { IgxIconModule, IgxGridModule, IgxExcelExporterService, IgxCsvExporterService } from 'igniteui-angular';
import { SharedModule } from './shared/shared.module';
@@ -48,6 +48,7 @@ import { GridPerformanceSampleComponent } from './grid-performance/grid-performa
import { GridSelectionComponent } from './grid-selection/grid-selection.sample';
import { GridVirtualizationSampleComponent } from './grid-remote-virtualization/grid-remote-virtualization.sample';
import { ButtonGroupSampleComponent } from './buttonGroup/buttonGroup.sample';
+import { RadioSampleComponent } from './radio/radio.sample';
const components = [
@@ -91,7 +92,7 @@ const components = [
CustomContentComponent,
ColorsSampleComponent,
ShadowsSampleComponent,
- TypographySampleComponent
+ RadioSampleComponent
];
@NgModule({
@@ -100,6 +101,7 @@ const components = [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
+ ReactiveFormsModule,
HttpClientModule,
IgxIconModule.forRoot(),
IgxGridModule.forRoot(),
diff --git a/src/app/radio/radio.sample.css b/src/app/radio/radio.sample.css
new file mode 100644
index 00000000000..ce2d920573c
--- /dev/null
+++ b/src/app/radio/radio.sample.css
@@ -0,0 +1,8 @@
+.sample-content {
+ flex-flow: column nowrap;
+}
+
+.radio-sample {
+ padding: 6px;
+}
+
diff --git a/src/app/radio/radio.sample.html b/src/app/radio/radio.sample.html
new file mode 100644
index 00000000000..e05c2075c58
--- /dev/null
+++ b/src/app/radio/radio.sample.html
@@ -0,0 +1,68 @@
+
+
+ Demonstrates a group of radio buttons.
+
+
+
diff --git a/src/app/radio/radio.sample.ts b/src/app/radio/radio.sample.ts
new file mode 100644
index 00000000000..cef225cec24
--- /dev/null
+++ b/src/app/radio/radio.sample.ts
@@ -0,0 +1,79 @@
+import { Component, ViewChild, AfterContentInit } from '@angular/core';
+import { IgxRadioGroupDirective } from 'igniteui-angular';
+import { FormGroup, FormBuilder } from '@angular/forms';
+
+class Person {
+ favoriteSeason: string;
+
+ constructor(public name: string, season?: string) {
+ if (season) {
+ this.favoriteSeason = season;
+ }
+ }
+}
+
+@Component({
+ selector: 'app-radio-sample',
+ styleUrls: ['radio.sample.css'],
+ templateUrl: 'radio.sample.html'
+})
+export class RadioSampleComponent implements AfterContentInit {
+ @ViewChild('radioGroupZZ', { read: IgxRadioGroupDirective }) public radioGroup: IgxRadioGroupDirective;
+
+ selectedValue: any;
+
+ seasons = [
+ 'Winter',
+ 'Spring',
+ 'Summer',
+ 'Autumn',
+ ];
+
+ personBob: Person = new Person('Bob', this.seasons[2]);
+
+ newPerson: Person;
+ personKirk: Person = new Person('Kirk', this.seasons[1]);
+ personKirkForm: FormGroup;
+
+ constructor(private _formBuilder: FormBuilder) {
+ this._createPersonKirkForm();
+ }
+
+ get diagnostic() {
+ return JSON.stringify(this.personBob);
+ }
+
+ ngAfterContentInit(): void {
+ setTimeout(() => this.selectedValue = this.radioGroup.value);
+ }
+
+ onBtnClick(evt) {
+ this.radioGroup.value = 'Baz';
+ }
+
+ onUpdateBtnClick(evt) {
+ const formModel = this.personKirkForm.value;
+
+ this.newPerson = new Person(formModel.name as string, formModel.favoriteSeason as string);
+ }
+
+ onRadioChange(evt) {
+ this.selectedValue = evt.value;
+ }
+
+ onSubmit() {
+ this.personBob.favoriteSeason = this.seasons[1];
+ }
+
+ private _createPersonKirkForm() {
+ this.personKirkForm = this._formBuilder.group({
+ name: '',
+ favoriteSeason: ''
+ });
+
+ this.personKirkForm.setValue({
+ name: this.personKirk.name,
+ favoriteSeason: this.personKirk.favoriteSeason
+ });
+ }
+}
diff --git a/src/app/routing.ts b/src/app/routing.ts
index 99e80d1e33f..5c9dd53966a 100644
--- a/src/app/routing.ts
+++ b/src/app/routing.ts
@@ -36,6 +36,7 @@ import { GridPerformanceSampleComponent } from './grid-performance/grid-performa
import { GridSelectionComponent } from './grid-selection/grid-selection.sample';
import { GridVirtualizationSampleComponent } from './grid-remote-virtualization/grid-remote-virtualization.sample';
import { ButtonGroupSampleComponent } from './buttonGroup/buttonGroup.sample';
+import { RadioSampleComponent } from './radio/radio.sample';
const appRoutes = [
{
@@ -115,6 +116,10 @@ const appRoutes = [
path: 'progressbar',
component: ProgressbarSampleComponent
},
+ {
+ path: 'radio',
+ component: RadioSampleComponent
+ },
{
path: 'ripple',
component: RippleSampleComponent