Skip to content

Commit 30e83a9

Browse files
committed
feat(autocomplete): add value support
1 parent d4ab3d3 commit 30e83a9

File tree

8 files changed

+275
-37
lines changed

8 files changed

+275
-37
lines changed
Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,52 @@
11
<div class="demo-autocomplete">
2-
<md-input-container>
3-
<input mdInput placeholder="State" [mdAutocomplete]="auto">
4-
</md-input-container>
2+
<md-card>
3+
<div>Reactive value: {{ stateCtrl.value }}</div>
4+
<div>Reactive dirty: {{ stateCtrl.dirty }}</div>
55

6-
<md-autocomplete #auto="mdAutocomplete">
7-
<md-option *ngFor="let state of states" [value]="state.code"> {{ state.name }} </md-option>
8-
</md-autocomplete>
6+
<md-input-container>
7+
<input mdInput placeholder="State" [mdAutocomplete]="reactiveAuto" [formControl]="stateCtrl">
8+
</md-input-container>
9+
10+
<md-card-actions>
11+
<button md-button (click)="stateCtrl.reset()">RESET</button>
12+
<button md-button (click)="stateCtrl.setValue('California')">SET VALUE</button>
13+
<button md-button (click)="stateCtrl.enabled ? stateCtrl.disable() : stateCtrl.enable()">
14+
TOGGLE DISABLED
15+
</button>
16+
</md-card-actions>
17+
18+
</md-card>
19+
20+
<md-card>
21+
<div>TD value (currentState): {{ currentState }}</div>
22+
<div>TD dirty: {{ modelDir.dirty }}</div>
23+
24+
<md-input-container>
25+
<input mdInput placeholder="State" [mdAutocomplete]="tdAuto" [(ngModel)]="currentState" #modelDir="ngModel"
26+
(ngModelChange)="this.tdStates = filterStates(currentState)" [disabled]="tdDisabled">
27+
</md-input-container>
28+
29+
<md-card-actions>
30+
<button md-button (click)="modelDir.reset()">RESET</button>
31+
<button md-button (click)="currentState='California'">SET VALUE</button>
32+
<button md-button (click)="tdDisabled=!tdDisabled">
33+
TOGGLE DISABLED
34+
</button>
35+
</md-card-actions>
36+
37+
</md-card>
938
</div>
39+
40+
<md-autocomplete #reactiveAuto="mdAutocomplete">
41+
<md-option *ngFor="let state of reactiveStates" [value]="state.name">
42+
<span>{{ state.name }}</span>
43+
<span class="demo-secondary-text"> ({{state.code}}) </span>
44+
</md-option>
45+
</md-autocomplete>
46+
47+
<md-autocomplete #tdAuto="mdAutocomplete">
48+
<md-option *ngFor="let state of tdStates" [value]="state.name">
49+
<span>{{ state.name }}</span>
50+
<span class="demo-secondary-text"> ({{state.code}}) </span>
51+
</md-option>
52+
</md-autocomplete>
Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
1-
.demo-autocomplete {}
1+
.demo-autocomplete {
2+
display: flex;
3+
flex-flow: row wrap;
4+
5+
md-card {
6+
width: 350px;
7+
margin: 24px;
8+
}
9+
10+
md-input-container {
11+
margin-top: 16px;
12+
}
13+
}
14+
15+
.demo-secondary-text {
16+
color: rgba(0, 0, 0, 0.54);
17+
margin-left: 8px;
18+
}

src/demo-app/autocomplete/autocomplete-demo.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
import {Component} from '@angular/core';
1+
import {Component, OnDestroy, ViewEncapsulation} from '@angular/core';
2+
import {FormControl} from '@angular/forms';
3+
import {Subscription} from 'rxjs/Subscription';
24

35
@Component({
46
moduleId: module.id,
57
selector: 'autocomplete-demo',
68
templateUrl: 'autocomplete-demo.html',
79
styleUrls: ['autocomplete-demo.css'],
10+
encapsulation: ViewEncapsulation.None
811
})
9-
export class AutocompleteDemo {
12+
export class AutocompleteDemo implements OnDestroy {
13+
stateCtrl = new FormControl();
14+
currentState = '';
15+
16+
reactiveStates: any[];
17+
tdStates: any[];
18+
19+
reactiveValueSub: Subscription;
20+
tdDisabled = false;
21+
1022
states = [
1123
{code: 'AL', name: 'Alabama'},
1224
{code: 'AZ', name: 'Arizona'},
@@ -35,4 +47,21 @@ export class AutocompleteDemo {
3547
{code: 'WI', name: 'Wisconsin'},
3648
{code: 'WY', name: 'Wyoming'},
3749
];
50+
51+
constructor() {
52+
this.reactiveStates = this.states;
53+
this.tdStates = this.states;
54+
this.reactiveValueSub =
55+
this.stateCtrl.valueChanges.subscribe(val => this.reactiveStates = this.filterStates(val));
56+
57+
}
58+
59+
filterStates(val: string) {
60+
return val ? this.states.filter((s) => s.name.match(new RegExp(val, 'gi'))) : this.states;
61+
}
62+
63+
ngOnDestroy() {
64+
this.reactiveValueSub.unsubscribe();
65+
}
66+
3867
}

src/lib/autocomplete/_autocomplete-theme.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
$foreground: map-get($theme, foreground);
55
$background: map-get($theme, background);
66

7-
md-option {
7+
.md-autocomplete-panel {
88
background: md-color($background, card);
99
color: md-color($foreground, text);
10+
}
1011

12+
md-option {
1113
&.md-selected {
1214
background: md-color($background, card);
1315
color: md-color($foreground, text);

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import {Directive, ElementRef, Input, ViewContainerRef, OnDestroy} from '@angular/core';
1+
import {
2+
Directive, ElementRef, Input, ViewContainerRef, Optional, OnDestroy
3+
} from '@angular/core';
4+
import {NgControl} from '@angular/forms';
25
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
36
import {MdAutocomplete} from './autocomplete';
47
import {PositionStrategy} from '../core/overlay/position/position-strategy';
58
import {Observable} from 'rxjs/Observable';
69
import {Subscription} from 'rxjs/Subscription';
710
import 'rxjs/add/observable/merge';
11+
import {MdOptionSelectEvent} from '../core/option/option';
812

913
/** The panel needs a slight y-offset to ensure the input underline displays. */
1014
export const MD_AUTOCOMPLETE_PANEL_OFFSET = 6;
@@ -23,11 +27,15 @@ export class MdAutocompleteTrigger implements OnDestroy {
2327
/** The subscription to events that close the autocomplete panel. */
2428
private _closingActionsSubscription: Subscription;
2529

30+
/** The subscription to changes in the options list. */
31+
private _optionChangesSubscription: Subscription;
32+
2633
/* The autocomplete panel to be attached to this trigger. */
2734
@Input('mdAutocomplete') autocomplete: MdAutocomplete;
2835

2936
constructor(private _element: ElementRef, private _overlay: Overlay,
30-
private _viewContainerRef: ViewContainerRef) {}
37+
private _viewContainerRef: ViewContainerRef,
38+
@Optional() private _controlDir: NgControl) {}
3139

3240
ngOnDestroy() { this._destroyPanel(); }
3341

@@ -44,8 +52,9 @@ export class MdAutocompleteTrigger implements OnDestroy {
4452

4553
if (!this._overlayRef.hasAttached()) {
4654
this._overlayRef.attach(this._portal);
47-
this._closingActionsSubscription =
48-
this.panelClosingActions.subscribe(() => this.closePanel());
55+
this._resetClosingActionSub();
56+
this._optionChangesSubscription =
57+
this.autocomplete.options.changes.subscribe(() => this._resetClosingActionSub());
4958
}
5059

5160
this._panelOpen = true;
@@ -58,6 +67,7 @@ export class MdAutocompleteTrigger implements OnDestroy {
5867
}
5968

6069
this._closingActionsSubscription.unsubscribe();
70+
this._optionChangesSubscription.unsubscribe();
6171
this._panelOpen = false;
6272
}
6373

@@ -75,6 +85,20 @@ export class MdAutocompleteTrigger implements OnDestroy {
7585
return this.autocomplete.options.map(option => option.onSelect);
7686
}
7787

88+
/**
89+
* Drops the current closing action subscription and re-creates it.
90+
* Used whenever the option list changes to ensure that new options
91+
* can be selected properly.
92+
*/
93+
private _resetClosingActionSub(): void {
94+
if (this._closingActionsSubscription) {
95+
this._closingActionsSubscription.unsubscribe();
96+
}
97+
98+
this._closingActionsSubscription =
99+
this.panelClosingActions.subscribe(event => this._setValueAndClose(event));
100+
}
101+
78102
/** Destroys the autocomplete suggestion panel. */
79103
private _destroyPanel(): void {
80104
if (this._overlayRef) {
@@ -84,6 +108,23 @@ export class MdAutocompleteTrigger implements OnDestroy {
84108
}
85109
}
86110

111+
/**
112+
* This method closes the panel, and if a value is specified, also sets the associated
113+
* control to that value. It will also mark the control as dirty if this interaction
114+
* stemmed from the user.
115+
*/
116+
private _setValueAndClose(event: MdOptionSelectEvent | null): void {
117+
if (event) {
118+
// TODO(kara): revisit animation once floating placeholder is toggle-able
119+
this._controlDir.control.setValue(event.source.value);
120+
if (event.isUserInput) {
121+
this._controlDir.control.markAsDirty();
122+
}
123+
}
124+
125+
this.closePanel();
126+
}
127+
87128
private _createOverlay(): void {
88129
this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
89130
this._overlayRef = this._overlay.create(this._getOverlayConfig());

0 commit comments

Comments
 (0)