Skip to content

Commit 5def001

Browse files
karammalerba
authored andcommitted
feat(autocomplete): add value support (#2516)
1 parent 651440f commit 5def001

File tree

8 files changed

+273
-40
lines changed

8 files changed

+273
-40
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>Template-driven value (currentState): {{ currentState }}</div>
22+
<div>Template-driven 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-trigger.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import {
22
Directive, ElementRef, Input, ViewContainerRef, Optional, OnDestroy
33
} from '@angular/core';
4+
import {NgControl} from '@angular/forms';
45
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
56
import {MdAutocomplete} from './autocomplete';
67
import {PositionStrategy} from '../core/overlay/position/position-strategy';
78
import {Observable} from 'rxjs/Observable';
8-
import {Subscription} from 'rxjs/Subscription';
9+
import {MdOptionSelectEvent} from '../core/option/option';
910
import 'rxjs/add/observable/merge';
1011
import {Dir} from '../core/rtl/dir';
12+
import 'rxjs/add/operator/startWith';
13+
import 'rxjs/add/operator/switchMap';
14+
1115

1216
/** The panel needs a slight y-offset to ensure the input underline displays. */
1317
export const MD_AUTOCOMPLETE_PANEL_OFFSET = 6;
@@ -23,14 +27,12 @@ export class MdAutocompleteTrigger implements OnDestroy {
2327
private _portal: TemplatePortal;
2428
private _panelOpen: boolean = false;
2529

26-
/** The subscription to events that close the autocomplete panel. */
27-
private _closingActionsSubscription: Subscription;
28-
2930
/* The autocomplete panel to be attached to this trigger. */
3031
@Input('mdAutocomplete') autocomplete: MdAutocomplete;
3132

3233
constructor(private _element: ElementRef, private _overlay: Overlay,
33-
private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir) {}
34+
private _viewContainerRef: ViewContainerRef,
35+
@Optional() private _controlDir: NgControl, @Optional() private _dir: Dir) {}
3436

3537
ngOnDestroy() { this._destroyPanel(); }
3638

@@ -47,8 +49,7 @@ export class MdAutocompleteTrigger implements OnDestroy {
4749

4850
if (!this._overlayRef.hasAttached()) {
4951
this._overlayRef.attach(this._portal);
50-
this._closingActionsSubscription =
51-
this.panelClosingActions.subscribe(() => this.closePanel());
52+
this._subscribeToClosingActions();
5253
}
5354

5455
this._panelOpen = true;
@@ -60,7 +61,6 @@ export class MdAutocompleteTrigger implements OnDestroy {
6061
this._overlayRef.detach();
6162
}
6263

63-
this._closingActionsSubscription.unsubscribe();
6464
this._panelOpen = false;
6565
}
6666

@@ -78,6 +78,25 @@ export class MdAutocompleteTrigger implements OnDestroy {
7878
return this.autocomplete.options.map(option => option.onSelect);
7979
}
8080

81+
82+
/**
83+
* This method listens to a stream of panel closing actions and resets the
84+
* stream every time the option list changes.
85+
*/
86+
private _subscribeToClosingActions(): void {
87+
// Every time the option list changes...
88+
this.autocomplete.options.changes
89+
// and also at initialization, before there are any option changes...
90+
.startWith(null)
91+
// create a new stream of panelClosingActions, replacing any previous streams
92+
// that were created, and flatten it so our stream only emits closing events...
93+
.switchMap(() => this.panelClosingActions)
94+
// when the first closing event occurs...
95+
.first()
96+
// set the value, close the panel, and complete.
97+
.subscribe(event => this._setValueAndClose(event));
98+
}
99+
81100
/** Destroys the autocomplete suggestion panel. */
82101
private _destroyPanel(): void {
83102
if (this._overlayRef) {
@@ -87,6 +106,22 @@ export class MdAutocompleteTrigger implements OnDestroy {
87106
}
88107
}
89108

109+
/**
110+
* This method closes the panel, and if a value is specified, also sets the associated
111+
* control to that value. It will also mark the control as dirty if this interaction
112+
* stemmed from the user.
113+
*/
114+
private _setValueAndClose(event: MdOptionSelectEvent | null): void {
115+
if (event) {
116+
this._controlDir.control.setValue(event.source.value);
117+
if (event.isUserInput) {
118+
this._controlDir.control.markAsDirty();
119+
}
120+
}
121+
122+
this.closePanel();
123+
}
124+
90125
private _createOverlay(): void {
91126
this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
92127
this._overlayRef = this._overlay.create(this._getOverlayConfig());

0 commit comments

Comments
 (0)