Skip to content

Commit 10605eb

Browse files
authored
Merge branch 'master' into list
2 parents b2329cf + 4d97271 commit 10605eb

File tree

642 files changed

+11226
-5449
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

642 files changed

+11226
-5449
lines changed

CHANGELOG.md

Lines changed: 109 additions & 0 deletions
Large diffs are not rendered by default.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License
22

3-
Copyright (c) 2017 Google, Inc.
3+
Copyright (c) 2017 Google LLC.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This is the home for the Angular team's Material Design components built for and
99
[Documentation, demos, and guides][aio] |
1010
[Google group](https://groups.google.com/forum/#!forum/angular-material2) |
1111
[Contributing](https://github.com/angular/material2/blob/master/CONTRIBUTING.md) |
12-
[Plunker Template](http://plnkr.co/edit/o077B6uEiiIgkC0S06dd?p=preview) |
12+
[Plunker Template](http://plnkr.co/edit/LSgU9X5sYVhx4bc3LBWm?p=preview) |
1313
[StackBlitz Template](https://stackblitz.com/edit/angular-material2-issue?file=app%2Fapp.component.ts)
1414

1515
### Getting started

build-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const angularVersion = package.dependencies['@angular/core'];
1515
/** License that will be placed inside of all created bundles. */
1616
const buildLicense = `/**
1717
* @license
18-
* Copyright Google Inc. All Rights Reserved.
18+
* Copyright Google LLC All Rights Reserved.
1919
*
2020
* Use of this source code is governed by an MIT-style license that can be
2121
* found in the LICENSE file at https://angular.io/license

e2e/components/icon-e2e.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('icon', () => {
1313
it('should have the correct class when used', async () => {
1414
const attr = await testIcon.getAttribute('class');
1515

16-
expect(attr).toContain('md-24');
16+
expect(attr).toContain('custom-class');
1717
expect(attr).toContain('material-icons');
1818
});
1919

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
It is possible to create custom form field controls that can be used inside `<mat-form-field>`. This
2+
can be useful if you need to create a component that shares a lot of common behavior with a form
3+
field, but adds some additional logic.
4+
5+
For example in this guide we'll learn how to create a custom input for inputting US telephone
6+
numbers and hook it up to work with `<mat-form-field>`. Here is what we'll build by the end of this
7+
guide:
8+
9+
<!-- example(form-field-custom-control) -->
10+
11+
In order to learn how to build custom form field controls, let's start with a simple input component
12+
that we want to work inside the form field. For example, a phone number input that segments the
13+
parts of the number into their own inputs. (Note: this is not intended to be a robust component,
14+
just a starting point for us to learn.)
15+
16+
```ts
17+
class MyTel {
18+
constructor(public area: string, public exchange: string, public subscriber: string) {}
19+
}
20+
21+
@Component({
22+
selector: 'my-tel-input',
23+
template: `
24+
<div [formGroup]="parts">
25+
<input class="area" formControlName="area" size="3">
26+
<span>&ndash;</span>
27+
<input class="exchange" formControlName="exchange" size="3">
28+
<span>&ndash;</span>
29+
<input class="subscriber" formControlName="subscriber" size="4">
30+
</div>
31+
`,
32+
styles: [`
33+
div {
34+
display: flex;
35+
}
36+
input {
37+
border: none;
38+
background: none;
39+
padding: 0;
40+
outline: none;
41+
font: inherit;
42+
text-align: center;
43+
}
44+
`],
45+
})
46+
class MyTelInput {
47+
parts: FormGroup;
48+
49+
@Input()
50+
get value(): MyTel | null {
51+
let n = this.parts.value;
52+
if (n.area.length == 3 && n.exchange.length == 3 && n.subscriber.length == 4) {
53+
return new MyTel(n.area, n.exchange, n.subscriber);
54+
}
55+
return null;
56+
}
57+
set value(tel: MyTel | null) {
58+
tel = tel || new MyTel('', '', '');
59+
this.parts.setValue({area: tel.area, exchange: tel.exchange, subscriber: tel.subscriber});
60+
}
61+
62+
constructor(fb: FormBuilder) {
63+
this.parts = fb.group({
64+
'area': '',
65+
'exchange': '',
66+
'subscriber': '',
67+
});
68+
}
69+
}
70+
```
71+
72+
### Providing our component as a MatFormFieldControl
73+
The first step is to provide our new component as an implementation of the `MatFormFieldControl`
74+
interface that the `<mat-form-field>` knows how to work with. To do this, we will have our class
75+
implement `MatFormFieldControl`. Since this is a generic interface, we'll need to include a type
76+
parameter indicating the type of data our control will work with, in this case `MyTel`. We then add
77+
a provider to our component so that the form field will be able to inject it as a
78+
`MatFormFieldControl`.
79+
80+
```ts
81+
@Component({
82+
...
83+
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
84+
})
85+
class MyTelInput implements MatFormFieldControl<MyTel> {
86+
...
87+
}
88+
```
89+
90+
This sets up our component so it can work with `<mat-form-field>`, but now we need to implement the
91+
various methods and properties declared by the interface we just implemented. To learn more about
92+
the `MatFormFieldControl` interface, see its
93+
[definition](https://github.com/angular/material2/blob/master/src/lib/form-field/form-field-control.ts).
94+
(Unfortunately generated API docs are not available yet, but we'll go through the methods and
95+
properties in this guide.)
96+
97+
### Implementing the methods and properties of MatFormFieldControl
98+
#### `value`
99+
This property allows someone to set or get the value of our control. Its type should be the same
100+
type we used for the type parameter when we implemented `MatFormFieldControl`. Since our component
101+
already has a value property, we don't need to do anything for this one.
102+
103+
#### `stateChanges`
104+
Because the `<mat-form-field>` uses the `OnPush` change detection strategy, we need to let it know
105+
when something happens in the form field control that may require the form field to run change
106+
detection. We do this via the `stateChanges` property. So far the only thing the form field needs to
107+
know about is when the value changes. We'll need to emit on the stateChanges stream when that
108+
happens, and as we continue flushing out these properties we'll likely find more places we need to
109+
emit. We should also make sure to complete `stateChanges` when our component is destroyed.
110+
111+
```ts
112+
stateChanges = new Subject<void>();
113+
114+
set value(tel: MyTel | null) {
115+
...
116+
this.stateChanges.next();
117+
}
118+
119+
ngOnDestroy() {
120+
this.stateChanges.complete();
121+
}
122+
```
123+
124+
#### `id`
125+
This property should return the ID of an element in the component's template that we want the
126+
`<mat-form-field>` to associate all of its labels and hints with. In this case, we'll use the host
127+
element and just generate a unique ID for it.
128+
129+
```ts
130+
static nextId = 0;
131+
132+
@HostBinding() id = `my-tel-input-${MyTelInput.nextId++}`;
133+
```
134+
135+
#### `placeholder`
136+
This property allows us to tell the `<mat-form-field>` what to use as a placeholder. In this
137+
example, we'll do the same thing as `matInput` and `<mat-select>` and allow the user to specify it
138+
via an `@Input()`. Since the value of the placeholder may change over time, we need to make sure to
139+
trigger change detection in the parent form field by emitting on the `stateChanges` stream when the
140+
placeholder changes.
141+
142+
```ts
143+
@Input()
144+
get placeholder() {
145+
return this._placeholder;
146+
}
147+
set placeholder(plh) {
148+
this._placeholder = plh;
149+
this.stateChanges.next();
150+
}
151+
private _placeholder: string;
152+
```
153+
154+
#### `ngControl`
155+
This property allows the form field control to specify the `@angular/forms` control that is bound to
156+
this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just
157+
set this to `null` in our component. In any real component, you would probably want to implement
158+
`ControlValueAccessor` so that your component can work with `formControl` and `ngModel`.
159+
160+
```ts
161+
ngControl = null;
162+
```
163+
164+
If you did implement `ControlValueAccessor`, you could simply inject the `NgControl` and make it
165+
publicly available. (For additional information about `ControlValueAccessor` see the
166+
[API docs](https://angular.io/api/forms/ControlValueAccessor).)
167+
168+
```ts
169+
constructor(..., @Optional() @Self() public ngControl: NgControl) { ... }
170+
```
171+
172+
#### `focused`
173+
This property indicates whether or not the form field control should be considered to be in a
174+
focused state. When it is in a focused state, the form field is displayed with a solid color
175+
underline. For the purposes of our component, we want to consider it focused if any of the part
176+
inputs are focused. We can use the `FocusMonitor` from `@angular/cdk` to easily check this. We also
177+
need to remember to emit on the `stateChanges` stream so change detection can happen.
178+
179+
```ts
180+
focused = false;
181+
182+
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef,
183+
renderer: Renderer2) {
184+
...
185+
fm.monitor(elRef.nativeElement, renderer, true).subscribe(origin => {
186+
this.focused = !!origin;
187+
this.stateChanges.next();
188+
});
189+
}
190+
191+
ngOnDestroy() {
192+
...
193+
this.fm.stopMonitoring(this.elRef.nativeElement);
194+
}
195+
```
196+
197+
#### `empty`
198+
This property indicates whether the form field control is empty. For our control, we'll consider it
199+
empty if all of the parts are empty.
200+
201+
```ts
202+
get empty() {
203+
let n = this.parts.value;
204+
return !n.area && !n.exchange && !n.subscriber;
205+
}
206+
```
207+
208+
#### `shouldPlaceholderFloat`
209+
This property is used to indicate whether the placeholder should be in the floating position. We'll
210+
use the same logic as `matInput` and float the placeholder when the input is focused or non-empty.
211+
Since the placeholder will be overlapping our control when when it's not floating, we should hide
212+
the `` characters when it's not floating.
213+
214+
```ts
215+
@HostBinding('class.floating')
216+
get shouldPlaceholderFloat() {
217+
return this.focused || !this.empty;
218+
}
219+
```
220+
```css
221+
span {
222+
opacity: 0;
223+
transition: opacity 200ms;
224+
}
225+
:host.floating span {
226+
opacity: 1;
227+
}
228+
```
229+
230+
#### `required`
231+
This property is used to indicate whether the input is required. `<mat-form-field>` uses this
232+
information to add a required indicator to the placeholder. Again, we'll want to make sure we run
233+
change detection if the required state changes.
234+
235+
```ts
236+
@Input()
237+
get required() {
238+
return this._required;
239+
}
240+
set required(req) {
241+
this._required = coerceBooleanProperty(req);
242+
this.stateChanges.next();
243+
}
244+
private _required = false;
245+
```
246+
247+
#### `disabled`
248+
This property tells the form field when it should be in the disabled state. In addition to reporting
249+
the right state to the form field, we need to set the disabled state on the individual inputs that
250+
make up our component.
251+
252+
```ts
253+
@Input()
254+
get disabled() {
255+
return this._disabled;
256+
}
257+
set disabled(dis) {
258+
this._disabled = coerceBooleanProperty(dis);
259+
this.stateChanges.next();
260+
}
261+
private _disabled = false;
262+
```
263+
```html
264+
<input class="area" formControlName="area" size="3" [disabled]="disabled">
265+
<span>&ndash;</span>
266+
<input class="exchange" formControlName="exchange" size="3" [disabled]="disabled">
267+
<span>&ndash;</span>
268+
<input class="subscriber" formControlName="subscriber" size="4" [disabled]="disabled">
269+
```
270+
271+
#### `errorState`
272+
This property indicates whether the associated `NgControl` is in an error state. Since we're not
273+
using an `NgControl` in this example, we don't need to do anything other than just set it to `false`.
274+
275+
```ts
276+
errorState = false;
277+
```
278+
279+
#### `controlType`
280+
This property allows us to specify a unique string for the type of control in form field. The
281+
`<mat-form-field>` will add an additional class based on this type that can be used to easily apply
282+
special styles to a `<mat-form-field>` that contains a specific type of control. In this example
283+
we'll use `my-tel-input` as our control type which will result in the form field adding the class
284+
`mat-form-field-my-tel-input`.
285+
286+
```ts
287+
controlType = 'my-tel-input';
288+
```
289+
290+
#### `setAriaDescribedByIds(ids: string[])`
291+
This method is used by the `<mat-form-field>` to specify the IDs that should be used for the
292+
`aria-describedby` attribute of your component. The method has one parameter, the list of IDs, we
293+
just need to apply the given IDs to our host element.
294+
295+
```ts
296+
@HostBinding('attr.aria-describedby') describedBy = '';
297+
298+
setDescribedByIds(ids: string[]) {
299+
this.describedBy = ids.join(' ');
300+
}
301+
```
302+
303+
#### `onContainerClick(event: MouseEvent)`
304+
This method will be called when the form field is clicked on. It allows your component to hook in
305+
and handle that click however it wants. The method has one parameter, the `MouseEvent` for the
306+
click. In our case we'll just focus the first `<input>` if the user isn't about to click an
307+
`<input>` anyways.
308+
309+
```ts
310+
onContainerClick(event: MouseEvent) {
311+
if ((event.target as Element).tagName.toLowerCase() != 'input') {
312+
this.elRef.nativeElement.querySelector('input').focus();
313+
}
314+
}
315+
```
316+
317+
### Trying it out
318+
Now that we've fully implemented the interface, we're ready to try our component out! All we need to
319+
do is place it inside of a `<mat-form-field>`
320+
321+
```html
322+
<mat-form-field>
323+
<my-tel-input></my-tel-input>
324+
</mat-form-field>
325+
```
326+
327+
We also get all of the features that come with `<mat-form-field>` such as floating placeholder,
328+
prefix, suffix, hints, and errors (if we've given the form field an `NgControl` and correctly report
329+
the error state).
330+
331+
```html
332+
<mat-form-field>
333+
<my-tel-input placeholder="Phone number" required></my-tel-input>
334+
<mat-icon matPrefix>phone</mat-icon>
335+
<mat-hint>Include area code</mat-hint>
336+
</mat-form-field>
337+
```

guides/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ For existing apps, follow these steps to begin using Angular Material.
99
npm install --save @angular/material @angular/cdk
1010
```
1111

12+
#### Alternative: Snapshot Build
13+
1214
A snapshot build with the latest changes from master is also available. Note that this snapshot
1315
build should not be considered stable and may break between releases.
1416

guides/theming-your-components.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ In order to style your own components with Angular Material's tooling, the compo
44
### Using `@mixin` to automatically apply a theme
55

66
#### Why using `@mixin`
7-
The advantage of using a `@mixin` function is that when you change your theme, every file that uses it will be updated automatically.
8-
Calling it with a different theme argument allow multiple themes within the app or component.
7+
The advantage of using a `@mixin` function is that when you change your theme, every file that uses it will be automatically updated.
8+
Calling the `@mixin` with a different theme argument allows multiple themes within the app or component.
99

1010
#### How to use `@mixin`
11-
We can better theming our custom components adding a `@mixin` function to its theme file and then calling this function to apply a theme.
11+
We can better theme our custom components by adding a `@mixin` function to its theme file, and then call this function to apply a theme.
1212

1313
All you need is to create a `@mixin` function in the custom-component-theme.scss
1414

0 commit comments

Comments
 (0)