Skip to content

Commit 36427b8

Browse files
authored
Merge pull request #195 from cal-smith/select
fix(select): add ngModel support to select
2 parents 0c23372 + 5593dab commit 36427b8

File tree

2 files changed

+142
-13
lines changed

2 files changed

+142
-13
lines changed

src/select/select.component.ts

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,137 @@
1-
import { Component, Input } from "@angular/core";
1+
import {
2+
Component,
3+
Input,
4+
Output,
5+
ViewChild,
6+
ElementRef,
7+
HostListener,
8+
EventEmitter
9+
} from "@angular/core";
10+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
211

12+
/**
13+
* `ibm-select` provides a styled `select` component.
14+
*
15+
* Example:
16+
*
17+
* ```
18+
* <ibm-select [(ngModel)]="model">
19+
* <option value="default" disabled selected hidden>Choose an option</option>
20+
* <option value="option1">Option 1</option>
21+
* <option value="option2">Option 2</option>
22+
* <option value="option3">Option 3</option>
23+
* </ibm-select>
24+
* ```
25+
26+
*/
327
@Component({
428
selector: "ibm-select",
529
template: `
630
<div class="bx--form-item">
731
<div class="bx--select">
832
<label [attr.for]="id" class="bx--label">{{label}}</label>
9-
<select [attr.id]="id" class="bx--select-input">
33+
<select
34+
#select
35+
[attr.id]="id"
36+
[disabled]="disabled"
37+
(change)="onChange($event)"
38+
class="bx--select-input">
1039
<ng-content></ng-content>
1140
</select>
1241
<svg class="bx--select__arrow" width="10" height="5" viewBox="0 0 10 5">
1342
<path d="M0 0l5 4.998L10 0z" fill-rule="evenodd" />
1443
</svg>
1544
</div>
1645
</div>
17-
`
46+
`,
47+
providers: [
48+
{
49+
provide: NG_VALUE_ACCESSOR,
50+
useExisting: Select,
51+
multi: true
52+
}
53+
]
1854
})
19-
export class Select {
55+
export class Select implements ControlValueAccessor {
56+
/**
57+
* Tracks the total number of selects instantiated. Used to generate unique IDs
58+
*/
2059
static selectCount = 0;
60+
/**
61+
* Label for the select. Appears above the input.
62+
*/
2163
@Input() label = "Select label";
64+
/**
65+
* Sets the unique ID. Defaults to `select-${total count of selects instantiated}`
66+
*/
2267
@Input() id = `select-${Select.selectCount++}`;
68+
/**
69+
* Set to true to disable component.
70+
*/
71+
@Input() disabled = false;
72+
/**
73+
* emits the selected options `value`
74+
*/
75+
@Output() selected = new EventEmitter();
76+
77+
@ViewChild("select") select: ElementRef;
78+
79+
get value() {
80+
return this.select.nativeElement.value;
81+
}
82+
83+
set value(v) {
84+
this.select.nativeElement.value = v;
85+
}
86+
87+
/**
88+
* Receives a value from the model.
89+
*/
90+
writeValue(obj: any) {
91+
this.value = obj;
92+
}
93+
94+
/**
95+
* Registers a listener that notifies the model when the control updates
96+
*/
97+
registerOnChange(fn: any) {
98+
this.onChangeHandler = fn;
99+
}
100+
101+
/**
102+
* Registers a listener that notifies the model when the control is blurred
103+
*/
104+
registerOnTouched(fn: any) {
105+
this.onTouchedHandler = fn;
106+
}
107+
108+
/**
109+
* Sets the disabled state through the model
110+
*/
111+
setDisabledState(isDisabled: boolean) {
112+
this.disabled = isDisabled;
113+
}
114+
115+
/**
116+
* Handles the change event from the `select`.
117+
* Sends events to the change handler and emits a `selected` event.
118+
*/
119+
onChange(event) {
120+
this.onChangeHandler(event.target.value);
121+
this.selected.emit(event.target.value);
122+
}
123+
124+
/**
125+
* placeholder declarations. Replaced by the functions provided to `registerOnChange` and `registerOnTouched`
126+
*/
127+
private onChangeHandler = (_: any) => { };
128+
private onTouchedHandler = () => { };
129+
130+
/**
131+
* Listens for the host blurring, and notifies the model
132+
*/
133+
@HostListener("blur")
134+
private blur() {
135+
this.onTouchedHandler();
136+
}
23137
}

src/select/select.stories.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,30 @@ storiesOf("Select", module).addDecorator(
1414
template: `
1515
<ibm-select>
1616
<option value="" disabled selected hidden>Choose an option</option>
17-
<option value="solong">A much longer option that is worth having around to check how text flows</option>
18-
<optgroup label="Category 1">
19-
<option value="option1">Option 1</option>
20-
<option value="option2">Option 2</option>
21-
</optgroup>
22-
<optgroup label="Category 2">
23-
<option value="option1">Option 1</option>
24-
<option value="option2">Option 2</option>
25-
</optgroup>
17+
<option value="solong">A much longer option that is worth having around to check how text flows</option>
18+
<optgroup label="Category 1">
19+
<option value="option1">Option 1</option>
20+
<option value="option2">Option 2</option>
21+
</optgroup>
22+
<optgroup label="Category 2">
23+
<option value="option1">Option 1</option>
24+
<option value="option2">Option 2</option>
25+
</optgroup>
2626
</ibm-select>
2727
`
28+
}))
29+
.add("With ngModel", () => ({
30+
template: `
31+
<ibm-select [(ngModel)]="model">
32+
<option value="default" disabled selected hidden>Choose an option</option>
33+
<option value="option1">Option 1</option>
34+
<option value="option2">Option 2</option>
35+
<option value="option3">Option 3</option>
36+
</ibm-select>
37+
<br>
38+
<span>Selected: {{ model }}</span>
39+
`,
40+
props: {
41+
model: "default"
42+
}
2843
}));

0 commit comments

Comments
 (0)