Skip to content

Commit b8cb2df

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

File tree

9 files changed

+272
-41
lines changed

9 files changed

+272
-41
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: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
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';
10+
import {MdOptionSelectEvent} from '../core/option/option';
711
import 'rxjs/add/observable/merge';
12+
import 'rxjs/add/operator/startWith';
13+
import 'rxjs/add/operator/switchMap';
814

915
/** The panel needs a slight y-offset to ensure the input underline displays. */
1016
export const MD_AUTOCOMPLETE_PANEL_OFFSET = 6;
@@ -20,14 +26,12 @@ export class MdAutocompleteTrigger implements OnDestroy {
2026
private _portal: TemplatePortal;
2127
private _panelOpen: boolean = false;
2228

23-
/** The subscription to events that close the autocomplete panel. */
24-
private _closingActionsSubscription: Subscription;
25-
2629
/* The autocomplete panel to be attached to this trigger. */
2730
@Input('mdAutocomplete') autocomplete: MdAutocomplete;
2831

2932
constructor(private _element: ElementRef, private _overlay: Overlay,
30-
private _viewContainerRef: ViewContainerRef) {}
33+
private _viewContainerRef: ViewContainerRef,
34+
@Optional() private _controlDir: NgControl) {}
3135

3236
ngOnDestroy() { this._destroyPanel(); }
3337

@@ -44,8 +48,7 @@ export class MdAutocompleteTrigger implements OnDestroy {
4448

4549
if (!this._overlayRef.hasAttached()) {
4650
this._overlayRef.attach(this._portal);
47-
this._closingActionsSubscription =
48-
this.panelClosingActions.subscribe(() => this.closePanel());
51+
this._subscribeToClosingActions();
4952
}
5053

5154
this._panelOpen = true;
@@ -57,7 +60,6 @@ export class MdAutocompleteTrigger implements OnDestroy {
5760
this._overlayRef.detach();
5861
}
5962

60-
this._closingActionsSubscription.unsubscribe();
6163
this._panelOpen = false;
6264
}
6365

@@ -75,6 +77,19 @@ export class MdAutocompleteTrigger implements OnDestroy {
7577
return this.autocomplete.options.map(option => option.onSelect);
7678
}
7779

80+
81+
/**
82+
* This method listens to a stream of panel closing actions and resets the
83+
* stream every time the option list changes.
84+
*/
85+
private _subscribeToClosingActions(): void {
86+
this.autocomplete.options.changes
87+
.startWith(null)
88+
.switchMap(() => this.panelClosingActions)
89+
.first()
90+
.subscribe(event => this._setValueAndClose(event));
91+
}
92+
7893
/** Destroys the autocomplete suggestion panel. */
7994
private _destroyPanel(): void {
8095
if (this._overlayRef) {
@@ -84,6 +99,23 @@ export class MdAutocompleteTrigger implements OnDestroy {
8499
}
85100
}
86101

102+
/**
103+
* This method closes the panel, and if a value is specified, also sets the associated
104+
* control to that value. It will also mark the control as dirty if this interaction
105+
* stemmed from the user.
106+
*/
107+
private _setValueAndClose(event: MdOptionSelectEvent | null): void {
108+
if (event) {
109+
// TODO(kara): revisit animation once floating placeholder is toggle-able
110+
this._controlDir.control.setValue(event.source.value);
111+
if (event.isUserInput) {
112+
this._controlDir.control.markAsDirty();
113+
}
114+
}
115+
116+
this.closePanel();
117+
}
118+
87119
private _createOverlay(): void {
88120
this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
89121
this._overlayRef = this._overlay.create(this._getOverlayConfig());

0 commit comments

Comments
 (0)