<!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>