<!DOCTYPE html>
<html>
<head>
  <title>Angular Material Plunker</title>

  <!-- Load common libraries -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/2.1.1/typescript.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.4.1/core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>

  <!-- Configure SystemJS -->
  <script src="systemjs.config.js"></script>

  <script>
    System
      .import('main.ts')
      .catch(console.error.bind(console));
  </script>

  <!-- Load the Angular Material stylesheet -->
  <link href="https://unpkg.com/@angular/material/prebuilt-themes/indigo-pink.css" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <style>body { font-family: Roboto, Arial, sans-serif; }</style>
</head>

<body>
<form-field-error-example>Loading Material Docs example...</form-field-error-example>
</body>

</html>


<!-- Copyright 2017 Google Inc. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at http://angular.io/license -->
/** Add Transpiler for Typescript */
System.config({
  transpiler: 'typescript',
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  packages: {
    '.': {
      defaultExtension: 'ts'
    },
    'vendor': {
      defaultExtension: 'js'
    }
  }
});

System.config({
  map: {
    'main': 'main.js',

    // Angular specific mappings.
    '@angular/core': 'https://unpkg.com/@angular/core/bundles/core.umd.js',
    '@angular/animations': 'https://unpkg.com/@angular/animations/bundles/animations.umd.js',
    '@angular/common': 'https://unpkg.com/@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'https://unpkg.com/@angular/compiler/bundles/compiler.umd.js',
    '@angular/http': 'https://unpkg.com/@angular/http/bundles/http.umd.js',
    '@angular/forms': 'https://unpkg.com/@angular/forms/bundles/forms.umd.js',
    '@angular/router': 'https://unpkg.com/@angular/router/bundles/router.umd.js',
    '@angular/platform-browser': 'https://unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'https://unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/animations/browser': 'https://unpkg.com/@angular/animations/bundles/animations-browser.umd.js',
    '@angular/platform-browser/animations': 'https://unpkg.com/@angular/platform-browser/bundles/platform-browser-animations.umd.js',
    '@angular/material': 'https://unpkg.com/@angular/material/bundles/material.umd.js',
    '@angular/cdk': 'https://unpkg.com/@angular/cdk/bundles/cdk.umd.js',
    '@angular/cdk/a11y': 'https://unpkg.com/@angular/cdk/bundles/cdk-a11y.umd.js',
    '@angular/cdk/bidi': 'https://unpkg.com/@angular/cdk/bundles/cdk-bidi.umd.js',
    '@angular/cdk/coercion': 'https://unpkg.com/@angular/cdk/bundles/cdk-coercion.umd.js',
    '@angular/cdk/collections': 'https://unpkg.com/@angular/cdk/bundles/cdk-collections.umd.js',
    '@angular/cdk/keycodes': 'https://unpkg.com/@angular/cdk/bundles/cdk-keycodes.umd.js',
    '@angular/cdk/observers': 'https://unpkg.com/@angular/cdk/bundles/cdk-observers.umd.js',
    '@angular/cdk/overlay': 'https://unpkg.com/@angular/cdk/bundles/cdk-overlay.umd.js',
    '@angular/cdk/platform': 'https://unpkg.com/@angular/cdk/bundles/cdk-platform.umd.js',
    '@angular/cdk/portal': 'https://unpkg.com/@angular/cdk/bundles/cdk-portal.umd.js',
    '@angular/cdk/rxjs': 'https://unpkg.com/@angular/cdk/bundles/cdk-rxjs.umd.js',
    '@angular/cdk/scrolling': 'https://unpkg.com/@angular/cdk/bundles/cdk-scrolling.umd.js',
    '@angular/cdk/table': 'https://unpkg.com/@angular/cdk/bundles/cdk-table.umd.js',
    '@angular/cdk/stepper': 'https://unpkg.com/@angular/cdk/bundles/cdk-stepper.umd.js',

    // Rxjs mapping
    'rxjs': 'https://unpkg.com/rxjs',
  },
  packages: {
    // Thirdparty barrels.
    'rxjs': { main: 'index' },
  }
});
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {
  MatAutocompleteModule,
  MatButtonModule,
  MatButtonToggleModule,
  MatCardModule,
  MatCheckboxModule,
  MatChipsModule,
  MatDatepickerModule,
  MatDialogModule,
  MatExpansionModule,
  MatGridListModule,
  MatIconModule,
  MatInputModule,
  MatListModule,
  MatMenuModule,
  MatNativeDateModule,
  MatPaginatorModule,
  MatProgressBarModule,
  MatProgressSpinnerModule,
  MatRadioModule,
  MatRippleModule,
  MatSelectModule,
  MatSidenavModule,
  MatSliderModule,
  MatSlideToggleModule,
  MatSnackBarModule,
  MatSortModule,
  MatTableModule,
  MatTabsModule,
  MatToolbarModule,
  MatTooltipModule,
  MatStepperModule,
} from '@angular/material';
import {FormFieldErrorExample} from './form-field-error-example';
import {UiAutocompleteComponent} from './ui-autocomplete';
import {HttpModule} from '@angular/http';
import {CdkTableModule} from '@angular/cdk/table';

@NgModule({
  exports: [
    CdkTableModule,
    MatAutocompleteModule,
    MatButtonModule,
    MatButtonToggleModule,
    MatCardModule,
    MatCheckboxModule,
    MatChipsModule,
    MatStepperModule,
    MatDatepickerModule,
    MatDialogModule,
    MatExpansionModule,
    MatGridListModule,
    MatIconModule,
    MatInputModule,
    MatListModule,
    MatMenuModule,
    MatNativeDateModule,
    MatPaginatorModule,
    MatProgressBarModule,
    MatProgressSpinnerModule,
    MatRadioModule,
    MatRippleModule,
    MatSelectModule,
    MatSidenavModule,
    MatSliderModule,
    MatSlideToggleModule,
    MatSnackBarModule,
    MatSortModule,
    MatTableModule,
    MatTabsModule,
    MatToolbarModule,
    MatTooltipModule,
  ]
})
export class PlunkerMaterialModule {}

@NgModule({

  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpModule,
    PlunkerMaterialModule,
    MatNativeDateModule,
    ReactiveFormsModule,
  ],

  declarations: [FormFieldErrorExample, UiAutocompleteComponent],
  bootstrap: [FormFieldErrorExample, UiAutocompleteComponent],
  providers: []
})
export class PlunkerAppModule {}

platformBrowserDynamic().bootstrapModule(PlunkerAppModule);


/**  Copyright 2017 Google Inc. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at http://angular.io/license */
<div class="example-container" [formGroup]="testForm">
  <mat-form-field>
    <input matInput placeholder="Works without required" formControlName="email">
    <mat-error>{{getErrorMessage()}}</mat-error>
  </mat-form-field>
  HasErrorRequired: {{  this.testForm.controls.email.hasError('required') }}
  Touched: {{  this.testForm.controls.email.touched }}

  <ui-autocomplete placeholder="Works with required" [options]="options" formControlName="autocompleteValue2" [errorText]="getAutocompleteError2()"  [filter]="filterOptions" required></ui-autocomplete>
  HasErrorRequired: {{  this.testForm.controls.autocompleteValue2.hasError('required') }}
  Touched: {{  this.testForm.controls.autocompleteValue2.touched }}
  
  <ui-autocomplete placeholder="Does not show error" [options]="options" formControlName="autocompleteValue" [errorText]="getAutocompleteError()"  [filter]="filterOptions"></ui-autocomplete>
  HasErrorRequired: {{  this.testForm.controls.autocompleteValue.hasError('required') }}
  Touched: {{  this.testForm.controls.autocompleteValue.touched }}
  
  <ui-autocomplete placeholder="Works with required" [options]="options" formControlName="autocompleteValue3" [errorText]="getAutocompleteError3()"  [filter]="filterOptions" required></ui-autocomplete>
  HasErrorRequired: {{  this.testForm.controls.autocompleteValue3.hasError('required') }}
  Touched: {{  this.testForm.controls.autocompleteValue3.touched }}
  
  <ui-autocomplete placeholder="Does not show error" [options]="options" formControlName="autocompleteValue4" [errorText]="getAutocompleteError4()"  [filter]="filterOptions"></ui-autocomplete>
  HasErrorRequired: {{  this.testForm.controls.autocompleteValue4.hasError('required') }}
  Touched: {{  this.testForm.controls.autocompleteValue4.touched }}
</div>

<!-- Copyright 2017 Google Inc. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at http://angular.io/license -->
import {Component, OnInit} from '@angular/core';
import {FormBuilder,FormGroup, Validators} from '@angular/forms';

/** @title Form field with error messages */
@Component({
  selector: 'form-field-error-example',
  templateUrl: 'form-field-error-example.html',
  styleUrls: ['form-field-error-example.css']
})
export class FormFieldErrorExample implements OnInit {
  
  testForm: FormGroup;
  
  constructor (private formBuilder: FormBuilder) {}
  
  options = [
    {
      name: 'one'
    },
    {
      name: 'two'
    }
  ];
  
  ngOnInit () {
    this.testForm = this.formBuilder.group({
      email: ['', [Validators.required] ],
      autocompleteValue: ['', [Validators.required] ],
      autocompleteValue2: ['', [Validators.required] ],
      autocompleteValue3: ['', [Validators.required] ],
      autocompleteValue4: ['', [Validators.required] ]
    });
  }

  getErrorMessage() {
    return this.testForm.controls.email.hasError('required') ? 'You must enter a value' : '';
  }
  
  filterOptions (val: string, options: any[]): any[] {
    return val ? options.filter((option: any) => option.name.toLowerCase().indexOf(val.toLowerCase()) === 0) : options;
  }
  
  getAutocompleteError() {
    return this.testForm.controls.autocompleteValue.hasError('required') ? 'You must enter a value' : '';
  } 
  
  getAutocompleteError2() {
    return this.testForm.controls.autocompleteValue2.hasError('required') ? 'You must enter a value' : '';
  }  
  
  getAutocompleteError3() {
    return this.testForm.controls.autocompleteValue3.hasError('required') ? 'You must enter a value' : '';
  } 
  
  getAutocompleteError4() {
    return this.testForm.controls.autocompleteValue4.hasError('required') ? 'You must enter a value' : '';
  }
}


/**  Copyright 2017 Google Inc. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at http://angular.io/license */
.example-container {
  display: flex;
  flex-direction: column;
}

.example-container > * {
  width: 100%;
}
import { Component, ChangeDetectionStrategy, Input, TemplateRef, ContentChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatAutocomplete } from '@angular/material';

export interface AutocompleteOption {
  [option: string]: any;
}

@Component({
  selector: 'ui-autocomplete',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './ui-autocomplete.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: UiAutocompleteComponent,
      multi: true
    }
  ]
})

export class UiAutocompleteComponent implements ControlValueAccessor {

  // Required
  @Input() options: AutocompleteOption[];

  // Optional
  @Input() displayProp: string = 'name';
  @Input() selectProp: string;
  @Input() filter: (val: string, options: AutocompleteOption[]) => AutocompleteOption[];
  @Input() showSearchIcon: boolean = true;
  @Input() placeholder: string = 'Search';
  @Input() errorText: string = '';
  @Input() required: boolean = false;
  @Input() isLoading: boolean = false;
  @ContentChild(TemplateRef) optionTemplate: TemplateRef<AutocompleteOption>;

  // Internal
  _value:  AutocompleteOption | any; // Input value
  _options: AutocompleteOption[]; // Autocomplete
  auto: MatAutocomplete;
  onTouched: () => void = () => {};
  propagateChange: (_: any) => void = () => { console.error(`Missing "formControlName" property.`); };

  selectOption (val: any): void {
    this.currentValue = val.option.value;
  }

  displayWith (val: any): any {
    return val && typeof val === 'object' ? val[this.displayProp] : val;
  }

  // Allows for specific Option property to be set instead of whole object
  propagateSelectedProp (): void {
    if (this.selectProp) {
      this.propagateChange(this._value[this.selectProp]);
    } else {
      this.propagateChange(this._value);
    }
  }

  // Filter
  shouldFilter (): void {
    if (this.filter) {
      this._options = this.filter(this.currentValue, this.options);
    }
  }

  //
  // FormControl
  get currentValue () {
    return this._value;
  }

  set currentValue (val: string) {
    this._value = val;
    this.propagateSelectedProp();
    this.onTouched();
  }

  textChanged (text: any) {
    this.currentValue = text.trim();
    this.shouldFilter();
  }

  textBlur () {
    this.onTouched();
  }

  writeValue (val: any): void {
    this._options = this.options;
    // Set initial value
    if (val !== null) {
      this._value = val;
    }
  }

  registerOnChange (fn: any): void {
    this.propagateChange = fn;
  }

  registerOnTouched (fn: any): void {
    this.onTouched = fn;
  }
}
<mat-form-field>
  <mat-icon matPrefix *ngIf="showSearchIcon">search</mat-icon>
  <input
    [(ngModel)]="_value"
    matInput
    type="text"
    (input)="textChanged($event.target.value)"
    (blur)="textBlur()"
    [placeholder]="placeholder"
    [matAutocomplete]="auto"
    [required]="required"
  >
  <mat-icon matSuffix class="loading-spinner" *ngIf="isLoading">cached</mat-icon>
  <mat-error>{{ errorText }}</mat-error>
</mat-form-field>
<mat-autocomplete
  #auto="matAutocomplete"
  (optionSelected)="selectOption($event)"
  [displayWith]="displayWith.bind(this)"
>
  <mat-option
    *ngFor="let option of _options"
    [value]="option"
  >
    <div [ngTemplateOutlet]="optionTemplate" [ngOutletContext]="{ $implicit: option }">
      <span *ngIf="!optionTemplate">{{ option.name }}</span>
    </div>
  </mat-option>
</mat-autocomplete>