mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs(form-field): add docs, examples, and guide (#7424)
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
It is possible to create custom form field controls that can be used inside `<mat-form-field>`. This
|
||||
can be useful if you need to create a component that shares a lot of common behavior with a form
|
||||
field, but adds some additional logic.
|
||||
|
||||
For example in this guide we'll learn how to create a custom input for inputting US telephone
|
||||
numbers and hook it up to work with `<mat-form-field>`. Here is what we'll build by the end of this
|
||||
guide:
|
||||
|
||||
<!-- example(form-field-custom-control) -->
|
||||
|
||||
In order to learn how to build custom form field controls, let's start with a simple input component
|
||||
that we want to work inside the form field. For example, a phone number input that segments the
|
||||
parts of the number into their own inputs. (Note: this is not intended to be a robust component,
|
||||
just a starting point for us to learn.)
|
||||
|
||||
```ts
|
||||
class MyTel {
|
||||
constructor(public area: string, public exchange: string, public subscriber: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-tel-input',
|
||||
template: `
|
||||
<div [formGroup]="parts">
|
||||
<input class="area" formControlName="area" size="3">
|
||||
<span>–</span>
|
||||
<input class="exchange" formControlName="exchange" size="3">
|
||||
<span>–</span>
|
||||
<input class="subscriber" formControlName="subscriber" size="4">
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
input {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
font: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
`],
|
||||
})
|
||||
class MyTelInput {
|
||||
parts: FormGroup;
|
||||
|
||||
@Input()
|
||||
get value(): MyTel | null {
|
||||
let n = this.parts.value;
|
||||
if (n.area.length == 3 && n.exchange.length == 3 && n.subscriber.length == 4) {
|
||||
return new MyTel(n.area, n.exchange, n.subscriber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set value(tel: MyTel | null) {
|
||||
tel = tel || new MyTel('', '', '');
|
||||
this.parts.setValue({area: tel.area, exchange: tel.exchange, subscriber: tel.subscriber});
|
||||
}
|
||||
|
||||
constructor(fb: FormBuilder) {
|
||||
this.parts = fb.group({
|
||||
'area': '',
|
||||
'exchange': '',
|
||||
'subscriber': '',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Providing our component as a MatFormFieldControl
|
||||
The first step is to provide our new component as an implementation of the `MatFormFieldControl`
|
||||
interface that the `<mat-form-field>` knows how to work with. To do this, we will have our class
|
||||
implement `MatFormFieldControl`. Since this is a generic interface, we'll need to include a type
|
||||
parameter indicating the type of data our control will work with, in this case `MyTel`. We then add
|
||||
a provider to our component so that the form field will be able to inject it as a
|
||||
`MatFormFieldControl`.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
...
|
||||
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
|
||||
})
|
||||
class MyTelInput implements MatFormFieldControl<MyTel> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
This sets up our component so it can work with `<mat-form-field>`, but now we need to implement the
|
||||
various methods and properties declared by the interface we just implemented. To learn more about
|
||||
the `MatFormFieldControl` interface, see its
|
||||
[definition](https://github.com/angular/material2/blob/master/src/lib/form-field/form-field-control.ts).
|
||||
(Unfortunately generated API docs are not available yet, but we'll go through the methods and
|
||||
properties in this guide.)
|
||||
|
||||
### Implementing the methods and properties of MatFormFieldControl
|
||||
#### `value`
|
||||
This property allows someone to set or get the value of our control. Its type should be the same
|
||||
type we used for the type parameter when we implemented `MatFormFieldControl`. Since our component
|
||||
already has a value property, we don't need to do anything for this one.
|
||||
|
||||
#### `stateChanges`
|
||||
Because the `<mat-form-field>` uses the `OnPush` change detection strategy, we need to let it know
|
||||
when something happens in the form field control that may require the form field to run change
|
||||
detection. We do this via the `stateChanges` property. So far the only thing the form field needs to
|
||||
know about is when the value changes. We'll need to emit on the stateChanges stream when that
|
||||
happens, and as we continue flushing out these properties we'll likely find more places we need to
|
||||
emit. We should also make sure to complete `stateChanges` when our component is destroyed.
|
||||
|
||||
```ts
|
||||
stateChanges = new Subject<void>();
|
||||
|
||||
set value(tel: MyTel | null) {
|
||||
...
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.stateChanges.complete();
|
||||
}
|
||||
```
|
||||
|
||||
#### `id`
|
||||
This property should return the ID of an element in the component's template that we want the
|
||||
`<mat-form-field>` to associate all of its labels and hints with. In this case, we'll use the host
|
||||
element and just generate a unique ID for it.
|
||||
|
||||
```ts
|
||||
static nextId = 0;
|
||||
|
||||
@HostBinding() id = `my-tel-input-${MyTelInput.nextId++}`;
|
||||
```
|
||||
|
||||
#### `placeholder`
|
||||
This property allows us to tell the `<mat-form-field>` what to use as a placeholder. In this
|
||||
example, we'll do the same thing as `matInput` and `<mat-select>` and allow the user to specify it
|
||||
via an `@Input()`. Since the value of the placeholder may change over time, we need to make sure to
|
||||
trigger change detection in the parent form field by emitting on the `stateChanges` stream when the
|
||||
placeholder changes.
|
||||
|
||||
```ts
|
||||
@Input()
|
||||
get placeholder() {
|
||||
return this._placeholder;
|
||||
}
|
||||
set placeholder(plh) {
|
||||
this._placeholder = plh;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _placeholder: string;
|
||||
```
|
||||
|
||||
#### `ngControl`
|
||||
This property allows the form field control to specify the `@angular/forms` control that is bound to
|
||||
this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just
|
||||
set this to `null` in our component. In any real component, you would probably want to implement
|
||||
`ControlValueAccessor` so that your component can work with `formControl` and `ngModel`.
|
||||
|
||||
```ts
|
||||
ngControl = null;
|
||||
```
|
||||
|
||||
If you did implement `ControlValueAccessor`, you could simply inject the `NgControl` and make it
|
||||
publicly available. (For additional information about `ControlValueAccessor` see the
|
||||
[API docs](https://angular.io/api/forms/ControlValueAccessor).)
|
||||
|
||||
```ts
|
||||
constructor(..., @Optional() @Self() public ngControl: NgControl) { ... }
|
||||
```
|
||||
|
||||
#### `focused`
|
||||
This property indicates whether or not the form field control should be considered to be in a
|
||||
focused state. When it is in a focused state, the form field is displayed with a solid color
|
||||
underline. For the purposes of our component, we want to consider it focused if any of the part
|
||||
inputs are focused. We can use the `FocusMonitor` from `@angular/cdk` to easily check this. We also
|
||||
need to remember to emit on the `stateChanges` stream so change detection can happen.
|
||||
|
||||
```ts
|
||||
focused = false;
|
||||
|
||||
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef,
|
||||
renderer: Renderer2) {
|
||||
...
|
||||
fm.monitor(elRef.nativeElement, renderer, true).subscribe(origin => {
|
||||
this.focused = !!origin;
|
||||
this.stateChanges.next();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
...
|
||||
this.fm.stopMonitoring(this.elRef.nativeElement);
|
||||
}
|
||||
```
|
||||
|
||||
#### `empty`
|
||||
This property indicates whether the form field control is empty. For our control, we'll consider it
|
||||
empty if all of the parts are empty.
|
||||
|
||||
```ts
|
||||
get empty() {
|
||||
let n = this.parts.value;
|
||||
return !n.area && !n.exchange && !n.subscriber;
|
||||
}
|
||||
```
|
||||
|
||||
#### `shouldPlaceholderFloat`
|
||||
This property is used to indicate whether the placeholder should be in the floating position. We'll
|
||||
use the same logic as `matInput` and float the placeholder when the input is focused or non-empty.
|
||||
Since the placeholder will be overlapping our control when when it's not floating, we should hide
|
||||
the `–` characters when it's not floating.
|
||||
|
||||
```ts
|
||||
@HostBinding('class.floating')
|
||||
get shouldPlaceholderFloat() {
|
||||
return this.focused || !this.empty;
|
||||
}
|
||||
```
|
||||
```css
|
||||
span {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
:host.floating span {
|
||||
opacity: 1;
|
||||
}
|
||||
```
|
||||
|
||||
#### `required`
|
||||
This property is used to indicate whether the input is required. `<mat-form-field>` uses this
|
||||
information to add a required indicator to the placeholder. Again, we'll want to make sure we run
|
||||
change detection if the required state changes.
|
||||
|
||||
```ts
|
||||
@Input()
|
||||
get required() {
|
||||
return this._required;
|
||||
}
|
||||
set required(req) {
|
||||
this._required = coerceBooleanProperty(req);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _required = false;
|
||||
```
|
||||
|
||||
#### `disabled`
|
||||
This property tells the form field when it should be in the disabled state. In addition to reporting
|
||||
the right state to the form field, we need to set the disabled state on the individual inputs that
|
||||
make up our component.
|
||||
|
||||
```ts
|
||||
@Input()
|
||||
get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(dis) {
|
||||
this._disabled = coerceBooleanProperty(dis);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _disabled = false;
|
||||
```
|
||||
```html
|
||||
<input class="area" formControlName="area" size="3" [disabled]="disabled">
|
||||
<span>–</span>
|
||||
<input class="exchange" formControlName="exchange" size="3" [disabled]="disabled">
|
||||
<span>–</span>
|
||||
<input class="subscriber" formControlName="subscriber" size="4" [disabled]="disabled">
|
||||
```
|
||||
|
||||
#### `errorState`
|
||||
This property indicates whether the associated `NgControl` is in an error state. Since we're not
|
||||
using an `NgControl` in this example, we don't need to do anything other than just set it to `false`.
|
||||
|
||||
```ts
|
||||
errorState = false;
|
||||
```
|
||||
|
||||
#### `controlType`
|
||||
This property allows us to specify a unique string for the type of control in form field. The
|
||||
`<mat-form-field>` will add an additional class based on this type that can be used to easily apply
|
||||
special styles to a `<mat-form-field>` that contains a specific type of control. In this example
|
||||
we'll use `my-tel-input` as our control type which will result in the form field adding the class
|
||||
`mat-form-field-my-tel-input`.
|
||||
|
||||
```ts
|
||||
controlType = 'my-tel-input';
|
||||
```
|
||||
|
||||
#### `setAriaDescribedByIds(ids: string[])`
|
||||
This method is used by the `<mat-form-field>` to specify the IDs that should be used for the
|
||||
`aria-describedby` attribute of your component. The method has one parameter, the list of IDs, we
|
||||
just need to apply the given IDs to our host element.
|
||||
|
||||
```ts
|
||||
@HostBinding('attr.aria-describedby') describedBy = '';
|
||||
|
||||
setDescribedByIds(ids: string[]) {
|
||||
this.describedBy = ids.join(' ');
|
||||
}
|
||||
```
|
||||
|
||||
#### `onContainerClick(event: MouseEvent)`
|
||||
This method will be called when the form field is clicked on. It allows your component to hook in
|
||||
and handle that click however it wants. The method has one parameter, the `MouseEvent` for the
|
||||
click. In our case we'll just focus the first `<input>` if the user isn't about to click an
|
||||
`<input>` anyways.
|
||||
|
||||
```ts
|
||||
onContainerClick(event: MouseEvent) {
|
||||
if ((event.target as Element).tagName.toLowerCase() != 'input') {
|
||||
this.elRef.nativeElement.querySelector('input').focus();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Trying it out
|
||||
Now that we've fully implemented the interface, we're ready to try our component out! All we need to
|
||||
do is place it inside of a `<mat-form-field>`
|
||||
|
||||
```html
|
||||
<mat-form-field>
|
||||
<my-tel-input></my-tel-input>
|
||||
</mat-form-field>
|
||||
```
|
||||
|
||||
We also get all of the features that come with `<mat-form-field>` such as floating placeholder,
|
||||
prefix, suffix, hints, and errors (if we've given the form field an `NgControl` and correctly report
|
||||
the error state).
|
||||
|
||||
```html
|
||||
<mat-form-field>
|
||||
<my-tel-input placeholder="Phone number" required></my-tel-input>
|
||||
<mat-icon matPrefix>phone</mat-icon>
|
||||
<mat-hint>Include area code</mat-hint>
|
||||
</mat-form-field>
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
Please see the official documentation at https://material.angular.io/components/component/form-field
|
||||
@@ -13,7 +13,7 @@ import {NgControl} from '@angular/forms';
|
||||
/** An interface which allows a control to work inside of a `MatFormField`. */
|
||||
export abstract class MatFormFieldControl<T> {
|
||||
/** The value of the control. */
|
||||
value: T;
|
||||
value: T | null;
|
||||
|
||||
/**
|
||||
* Stream that emits whenever the state of the control changes such that the parent `MatFormField`
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
`<mat-form-field>` is a component used to wrap several Angular Material components and apply common
|
||||
[Text field](https://material.io/guidelines/components/text-fields.html) styles such as the
|
||||
underline, floating label, and hint messages.
|
||||
|
||||
In this document, "form field" refers to the wrapper component `<mat-form-field>` and
|
||||
"form field control" refers to the component that the `<mat-form-field>` is wrapping
|
||||
(e.g. the input, textarea, select, etc.)
|
||||
|
||||
The following Angular Material components are designed to work inside a `<mat-form-field>`:
|
||||
* [<input matInput> & <textarea matInput>](https://material.angular.io/components/input/overview)
|
||||
* [<mat-select>](https://material.angular.io/components/select/overview)
|
||||
* [<mat-chip-list>](https://material.angular.io/components/chips/overview)
|
||||
|
||||
<!-- example(form-field-overview) -->
|
||||
|
||||
### Floating placeholder
|
||||
The floating placeholder is a text label displayed on top of the form field control when
|
||||
the control does not contain any text. By default, when text is present the floating placeholder
|
||||
floats above the form field control.
|
||||
|
||||
Placeholder text can be specified using the `placeholder` property on the form field control, or
|
||||
by adding a `<mat-placeholder>` element inside the form field. Only one of these options should be
|
||||
used, specifying both will raise an error.
|
||||
|
||||
If the form field control is marked with a `required` attribute, an asterisk will be appended to the
|
||||
placeholder to indicate the fact that it is a required field. If unwanted, this can be disabled by
|
||||
setting the `hideRequiredMarker` property on `<mat-form-field>`
|
||||
|
||||
The `floatPlaceholder` property of `<mat-form-field>` can be used to change this default floating
|
||||
behavior. It can set to `never` to hide the placeholder instead of float it when text is present in
|
||||
the form field control. It can be set to `always` to float the placeholder even when no text is
|
||||
present in the form field control. It can also be set to `auto` to restore the default behavior.
|
||||
|
||||
<!-- example(form-field-placeholder) -->
|
||||
|
||||
Global default placeholder options can be specified by setting providing a value for
|
||||
`MAT_PLACEHOLDER_GLOBAL_OPTIONS` in your application's root module. Like the property, the global
|
||||
setting can be either `always`, `never`, or `auto`.
|
||||
|
||||
```ts
|
||||
@NgModule({
|
||||
providers: [
|
||||
{provide: MAT_PLACEHOLDER_GLOBAL_OPTIONS, useValue: {float: 'always'}}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Hint labels
|
||||
Hint labels are additional descriptive text that appears below the form field's underline. A
|
||||
`<mat-form-field>` can have up to two hint labels; one start-aligned (left in an LTR language, right
|
||||
in RTL), and one end-aligned.
|
||||
|
||||
Hint labels are specified in one of two ways: either by using the `hintLabel` property of
|
||||
`<mat-form-field>`, or by adding a `<mat-hint>` element inside the form field. When adding a hint
|
||||
via the `hintLabel` property, it will be treated as the start hint. Hints added via the
|
||||
`<mat-hint>` hint element can be added to either side by setting the `align` property on
|
||||
`<mat-hint>` to either `start` or `end`. Attempting to add multiple hints to the same side will
|
||||
raise an error.
|
||||
|
||||
<!-- example(form-field-hint) -->
|
||||
|
||||
### Error messages
|
||||
Error messages can be shown under the form field underline by adding `mat-error` elements inside the
|
||||
form field. Errors are hidden initially and will be displayed on invalid form fields after the user
|
||||
has interacted with the element or the parent form has been submitted. Since the errors occupy the
|
||||
same space as the hints, the hints are hidden when the errors are shown.
|
||||
|
||||
If a form field can have more than one error state, it is up to the consumer to toggle which
|
||||
messages should be displayed. This can be done with CSS, `ngIf` or `ngSwitch`. Multiple error
|
||||
messages can be shown at the same time if desired, but the `<mat-form-field>` only reserves enough
|
||||
space to display one error message at a time. Ensuring that enough space is available to display
|
||||
multiple errors is up to the user.
|
||||
|
||||
<!-- example(form-field-error) -->
|
||||
|
||||
### Prefix & suffix
|
||||
Custom content can be included before and after the input tag, as a prefix or suffix. It will be
|
||||
included within the visual container that wraps the form control as per the Material specification.
|
||||
|
||||
Adding the `matPrefix` directive to an element inside the `<mat-form-field>` will designate it as
|
||||
the prefix. Similarly, adding `matSuffix` will designate it as the suffix.
|
||||
|
||||
<!-- example(form-field-prefix-suffix) -->
|
||||
|
||||
### Custom form field controls
|
||||
In addition to the form field controls that Angular Material provides, it is possible to create
|
||||
custom form field controls that work with `<mat-form-field>` in the same way. For additional
|
||||
information on this see the guide on
|
||||
[Creating Custom mat-form-field Controls](https://material.angular.io/guide/creating-a-custom-form-field-control).
|
||||
|
||||
### Theming
|
||||
`<mat-form-field>` has a `color` property which can be set to `primary`, `accent`, or `warn`. This
|
||||
will set the color of the form field underline and floating placeholder based on the theme colors
|
||||
of your app.
|
||||
|
||||
`<mat-form-field>` inherits its `font-size` from its parent element. This can be overridden to an
|
||||
explicit size using CSS. We recommend a specificity of at least 1 element + 1 class.
|
||||
|
||||
```css
|
||||
mat-form-field.mat-form-field {
|
||||
font-size: 16px;
|
||||
}
|
||||
```
|
||||
|
||||
<!-- example(form-field-theming) -->
|
||||
|
||||
### Accessibility
|
||||
If a floating placeholder is specified, it will be automatically used as the label for the form field
|
||||
control. If no floating placeholder is specified, the user should label the form field control
|
||||
themselves using `aria-label`, `aria-labelledby` or `<label for=...>`.
|
||||
|
||||
Any errors and hints added to the form field are automatically added to the form field control's
|
||||
`aria-describedby` set.
|
||||
|
||||
### Troubleshooting
|
||||
#### Error: Placeholder attribute and child element were both specified
|
||||
This error occurs when you have specified two conflicting placeholders. Make sure that you haven't
|
||||
included both a `placeholder` property on your form field control and a `<mat-placeholder>`
|
||||
element.
|
||||
|
||||
#### Error: A hint was already declared for align="..."
|
||||
This error occurs if you have added multiple hints for the same side. Keep in mind that the
|
||||
`hintLabel` property adds a hint to the start side.
|
||||
|
||||
#### Error: mat-form-field must contain a MatFormFieldControl
|
||||
This error occurs when you have not added a form field control to your form field. If your form
|
||||
field contains a native `<input>` or `<textarea>` element, make sure you've added the `matInput`
|
||||
directive to it. Other components that can act as a form field control include `<mat-select>`,
|
||||
`<mat-chip-list>`, and any custom form field controls you've created.
|
||||
@@ -41,6 +41,13 @@ import {DialogElementsExampleDialog,DialogElementsExample} from './dialog-elemen
|
||||
import {DialogOverviewExampleDialog,DialogOverviewExample} from './dialog-overview/dialog-overview-example';
|
||||
import {ExpansionOverviewExample} from './expansion-overview/expansion-overview-example';
|
||||
import {ExpansionStepsExample} from './expansion-steps/expansion-steps-example';
|
||||
import {MyTelInput,FormFieldCustomControlExample} from './form-field-custom-control/form-field-custom-control-example';
|
||||
import {FormFieldErrorExample} from './form-field-error/form-field-error-example';
|
||||
import {FormFieldHintExample} from './form-field-hint/form-field-hint-example';
|
||||
import {FormFieldOverviewExample} from './form-field-overview/form-field-overview-example';
|
||||
import {FormFieldPlaceholderExample} from './form-field-placeholder/form-field-placeholder-example';
|
||||
import {FormFieldPrefixSuffixExample} from './form-field-prefix-suffix/form-field-prefix-suffix-example';
|
||||
import {FormFieldThemingExample} from './form-field-theming/form-field-theming-example';
|
||||
import {GridListDynamicExample} from './grid-list-dynamic/grid-list-dynamic-example';
|
||||
import {GridListOverviewExample} from './grid-list-overview/grid-list-overview-example';
|
||||
import {IconOverviewExample} from './icon-overview/icon-overview-example';
|
||||
@@ -260,6 +267,48 @@ export const EXAMPLE_COMPONENTS = {
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-custom-control': {
|
||||
title: 'Form field with custom telephone number input control. ',
|
||||
component: FormFieldCustomControlExample,
|
||||
additionalFiles: ["form-field-custom-control-example.html"],
|
||||
selectorName: 'FormFieldCustomControlExample, MyTelInput'
|
||||
},
|
||||
'form-field-error': {
|
||||
title: 'Form field with error messages ',
|
||||
component: FormFieldErrorExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-hint': {
|
||||
title: 'Form field with hints ',
|
||||
component: FormFieldHintExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-overview': {
|
||||
title: 'Simple form field ',
|
||||
component: FormFieldOverviewExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-placeholder': {
|
||||
title: 'Form field with placeholder ',
|
||||
component: FormFieldPlaceholderExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-prefix-suffix': {
|
||||
title: 'Form field with prefix & suffix ',
|
||||
component: FormFieldPrefixSuffixExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'form-field-theming': {
|
||||
title: 'Form field theming ',
|
||||
component: FormFieldThemingExample,
|
||||
additionalFiles: null,
|
||||
selectorName: null
|
||||
},
|
||||
'grid-list-dynamic': {
|
||||
title: 'Dynamic grid-list',
|
||||
component: GridListDynamicExample,
|
||||
@@ -585,6 +634,13 @@ export const EXAMPLE_LIST = [
|
||||
DialogOverviewExampleDialog,DialogOverviewExample,
|
||||
ExpansionOverviewExample,
|
||||
ExpansionStepsExample,
|
||||
MyTelInput,FormFieldCustomControlExample,
|
||||
FormFieldErrorExample,
|
||||
FormFieldHintExample,
|
||||
FormFieldOverviewExample,
|
||||
FormFieldPlaceholderExample,
|
||||
FormFieldPrefixSuffixExample,
|
||||
FormFieldThemingExample,
|
||||
GridListDynamicExample,
|
||||
GridListOverviewExample,
|
||||
IconOverviewExample,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
font: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
:host.floating span {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div [formGroup]="parts">
|
||||
<input class="area" formControlName="area" size="3" [disabled]="disabled">
|
||||
<span>–</span>
|
||||
<input class="exchange" formControlName="exchange" size="3" [disabled]="disabled">
|
||||
<span>–</span>
|
||||
<input class="subscriber" formControlName="subscriber" size="4" [disabled]="disabled">
|
||||
</div>
|
||||
@@ -0,0 +1,137 @@
|
||||
import {FocusMonitor} from '@angular/cdk/a11y';
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {Component, ElementRef, HostBinding, Input, OnDestroy, Renderer2} from '@angular/core';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
import {MatFormFieldControl} from '@angular/material/form-field';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
|
||||
/** Data structure for holding telephone number. */
|
||||
export class MyTel {
|
||||
constructor(public area: string, public exchange: string, public subscriber: string) {}
|
||||
}
|
||||
|
||||
|
||||
/** Custom `MatFormFieldControl` for telephone number input. */
|
||||
@Component({
|
||||
selector: 'my-tel-input',
|
||||
templateUrl: 'form-field-custom-control-example.html',
|
||||
styleUrls: ['form-field-custom-control-example.css'],
|
||||
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
|
||||
})
|
||||
export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
|
||||
static nextId = 0;
|
||||
|
||||
parts: FormGroup;
|
||||
|
||||
stateChanges = new Subject<void>();
|
||||
|
||||
focused = false;
|
||||
|
||||
ngControl = null;
|
||||
|
||||
errorState = false;
|
||||
|
||||
controlType = 'my-tel-input';
|
||||
|
||||
get empty() {
|
||||
let n = this.parts.value;
|
||||
return !n.area && !n.exchange && !n.subscriber;
|
||||
}
|
||||
|
||||
@HostBinding('class.floating')
|
||||
get shouldPlaceholderFloat() {
|
||||
return this.focused || !this.empty;
|
||||
}
|
||||
|
||||
@HostBinding() id = `my-tel-input-${MyTelInput.nextId++}`;
|
||||
|
||||
@HostBinding('attr.aria-describedby') describedBy = '';
|
||||
|
||||
@Input()
|
||||
get placeholder() {
|
||||
return this._placeholder;
|
||||
}
|
||||
set placeholder(plh) {
|
||||
this._placeholder = plh;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _placeholder: string;
|
||||
|
||||
@Input()
|
||||
get required() {
|
||||
return this._required;
|
||||
}
|
||||
set required(req) {
|
||||
this._required = coerceBooleanProperty(req);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _required = false;
|
||||
|
||||
@Input()
|
||||
get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(dis) {
|
||||
this._disabled = coerceBooleanProperty(dis);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
private _disabled = false;
|
||||
|
||||
@Input()
|
||||
get value(): MyTel | null {
|
||||
let n = this.parts.value;
|
||||
if (n.area.length == 3 && n.exchange.length == 3 && n.subscriber.length == 4) {
|
||||
return new MyTel(n.area, n.exchange, n.subscriber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set value(tel: MyTel | null) {
|
||||
tel = tel || new MyTel('', '', '');
|
||||
this.parts.setValue({area: tel.area, exchange: tel.exchange, subscriber: tel.subscriber});
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef,
|
||||
renderer: Renderer2) {
|
||||
this.parts = fb.group({
|
||||
'area': '',
|
||||
'exchange': '',
|
||||
'subscriber': '',
|
||||
});
|
||||
|
||||
fm.monitor(elRef.nativeElement, renderer, true).subscribe((origin) => {
|
||||
this.focused = !!origin;
|
||||
this.stateChanges.next();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.stateChanges.complete();
|
||||
this.fm.stopMonitoring(this.elRef.nativeElement);
|
||||
}
|
||||
|
||||
setDescribedByIds(ids: string[]) {
|
||||
this.describedBy = ids.join(' ');
|
||||
}
|
||||
|
||||
onContainerClick(event: MouseEvent) {
|
||||
if ((event.target as Element).tagName.toLowerCase() != 'input') {
|
||||
this.elRef.nativeElement.querySelector('input').focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @title Form field with custom telephone number input control. */
|
||||
@Component({
|
||||
selector: 'form-field-custom-control-example',
|
||||
template: `
|
||||
<mat-form-field>
|
||||
<my-tel-input placeholder="Phone number" required></my-tel-input>
|
||||
<mat-icon matSuffix>phone</mat-icon>
|
||||
<mat-hint>Include area code</mat-hint>
|
||||
</mat-form-field>
|
||||
`
|
||||
})
|
||||
export class FormFieldCustomControlExample {}
|
||||
@@ -0,0 +1,8 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="example-container">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Enter your email" [formControl]="email" required>
|
||||
<mat-error *ngIf="email.invalid">{{getErrorMessage()}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FormControl, 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 {
|
||||
email = new FormControl('', [Validators.required, Validators.email]);
|
||||
|
||||
getErrorMessage() {
|
||||
return this.email.hasError('required') ? 'You must enter a value' :
|
||||
this.email.hasError('email') ? 'Not a valid email' :
|
||||
'';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="example-container">
|
||||
<mat-form-field hintLabel="Max 10 characters">
|
||||
<input matInput #input maxlength="10" placeholder="Enter some input">
|
||||
<mat-hint align="end">{{input.value?.length || 0}}/10</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Select me">
|
||||
<mat-option value="option">Option</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint align="end">Here's the dropdown arrow ^</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
/** @title Form field with hints */
|
||||
@Component({
|
||||
selector: 'form-field-hint-example',
|
||||
templateUrl: 'form-field-hint-example.html',
|
||||
styleUrls: ['form-field-hint-example.css']
|
||||
})
|
||||
export class FormFieldHintExample {}
|
||||
@@ -0,0 +1,8 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="example-container">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Input">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<textarea matInput placeholder="Textarea"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Select">
|
||||
<mat-option value="option">Option</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
/** @title Simple form field */
|
||||
@Component({
|
||||
selector: 'form-field-overview-example',
|
||||
templateUrl: 'form-field-overview-example.html',
|
||||
styleUrls: ['form-field-overview-example.css']
|
||||
})
|
||||
export class FormFieldOverviewExample {}
|
||||
@@ -0,0 +1,20 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.example-container form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.example-container form > * {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.example-container .mat-radio-button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="example-container">
|
||||
<form class="example-container" [formGroup]="options">
|
||||
<mat-checkbox formControlName="hideRequired">Hide required marker</mat-checkbox>
|
||||
<div>
|
||||
<label>Float placeholder: </label>
|
||||
<mat-radio-group formControlName="floatPlaceholder">
|
||||
<mat-radio-button value="auto">Auto</mat-radio-button>
|
||||
<mat-radio-button value="always">Always</mat-radio-button>
|
||||
<mat-radio-button value="never">Never</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<mat-form-field
|
||||
[hideRequiredMarker]="options.value.hideRequired"
|
||||
[floatPlaceholder]="options.value.floatPlaceholder">
|
||||
<input matInput placeholder="Simple placeholder" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field
|
||||
[hideRequiredMarker]="options.value.hideRequired"
|
||||
[floatPlaceholder]="options.value.floatPlaceholder">
|
||||
<mat-select required>
|
||||
<mat-option>-- None --</mat-option>
|
||||
<mat-option value="option">Option</mat-option>
|
||||
</mat-select>
|
||||
<mat-placeholder><mat-icon>favorite</mat-icon> <b> Fancy</b> <i> placeholder</i></mat-placeholder>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
|
||||
/** @title Form field with placeholder */
|
||||
@Component({
|
||||
selector: 'form-field-placeholder-example',
|
||||
templateUrl: 'form-field-placeholder-example.html',
|
||||
styleUrls: ['form-field-placeholder-example.css']
|
||||
})
|
||||
export class FormFieldPlaceholderExample {
|
||||
options: FormGroup;
|
||||
|
||||
constructor(fb: FormBuilder) {
|
||||
this.options = fb.group({
|
||||
hideRequired: false,
|
||||
floatPlaceholder: 'auto',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.example-right-align {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input.example-right-align::-webkit-outer-spin-button,
|
||||
input.example-right-align::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="example-container">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Enter your password" [type]="hide ? 'password' : 'text'">
|
||||
<mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility' : 'visibility_off'}}</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Amount" type="number" class="example-right-align">
|
||||
<span matPrefix>$ </span>
|
||||
<span matSuffix>.00</span>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
/** @title Form field with prefix & suffix */
|
||||
@Component({
|
||||
selector: 'form-field-prefix-suffix-example',
|
||||
templateUrl: 'form-field-prefix-suffix-example.html',
|
||||
styleUrls: ['form-field-prefix-suffix-example.css']
|
||||
})
|
||||
export class FormFieldPrefixSuffixExample {
|
||||
hide = true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.example-container > * {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<form class="example-container" [formGroup]="options" [style.fontSize.px]="getFontSize()">
|
||||
<mat-form-field [color]="options.value.color">
|
||||
<mat-select placeholder="Color" formControlName="color">
|
||||
<mat-option value="primary">Primary</mat-option>
|
||||
<mat-option value="accent">Accent</mat-option>
|
||||
<mat-option value="warn">Warn</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field [color]="options.value.color">
|
||||
<input matInput type="number" placeholder="Font size (px)" formControlName="fontSize" min="10">
|
||||
<mat-error *ngIf="options.get('fontSize').invalid">Min size: 10px</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
@@ -0,0 +1,23 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
|
||||
/** @title Form field theming */
|
||||
@Component({
|
||||
selector: 'form-field-theming-example',
|
||||
templateUrl: 'form-field-theming-example.html',
|
||||
styleUrls: ['form-field-theming-example.css']
|
||||
})
|
||||
export class FormFieldThemingExample {
|
||||
options: FormGroup;
|
||||
|
||||
constructor(fb: FormBuilder) {
|
||||
this.options = fb.group({
|
||||
'color': 'primary',
|
||||
'fontSize': [16, Validators.min(10)],
|
||||
});
|
||||
}
|
||||
|
||||
getFontSize() {
|
||||
return Math.max(10, this.options.value.fontSize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user