This series explains how Angular Components are working by diving its source code deeply.
MatDivider
MatDivider is one of the simplest component in the Angular Material library.
It just can display a line separator but its source code is worth to read enough.
https://github.com/angular/components/blob/master/src/material/divider/divider.ts
@Component({
selector: 'mat-divider',
host: {
'role': 'separator',
'[attr.aria-orientation]': 'vertical ? "vertical" : "horizontal"',
'[class.mat-divider-vertical]': 'vertical',
'[class.mat-divider-horizontal]': '!vertical',
'[class.mat-divider-inset]': 'inset',
'class': 'mat-divider'
},
template: '',
styleUrls: ['divider.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatDivider {
/** Whether the divider is vertically aligned. */
@Input()
get vertical(): boolean { return this._vertical; }
set vertical(value: boolean) { this._vertical = coerceBooleanProperty(value); }
private _vertical: boolean = false;
/** Whether the divider is an inset divider. */
@Input()
get inset(): boolean { return this._inset; }
set inset(value: boolean) { this._inset = coerceBooleanProperty(value); }
private _inset: boolean = false;
static ngAcceptInputType_vertical: BooleanInput;
static ngAcceptInputType_inset: BooleanInput;
}
If you’ve understood every line above, you don’t need to read following parts.
Component Metadata
At first, look at the metadata of MatDivider
line by line.
@Component({
selector: 'mat-divider',
host: {
'role': 'separator',
'[attr.aria-orientation]': 'vertical ? "vertical" : "horizontal"',
'[class.mat-divider-vertical]': 'vertical',
'[class.mat-divider-horizontal]': '!vertical',
'[class.mat-divider-inset]': 'inset',
'class': 'mat-divider'
},
template: '',
styleUrls: ['divider.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
selector
The selector
metadata is a CSS selector of the component. If you don’t know this, you may need to go back to getting started.
host
The host
metadata is a map of binding to host element. It can accept template syntax same as inner template.
https://angular.io/api/core/Directive#host
'role': 'separator'
<mat-divider>
host element has always role="separator"
attribute. This is an ARIA role attribute. This arrtibute tells the User Agent this non-built-in HTML tag is a separator.
The Roles Model | Accessible Rich Internet Applications (WAI-ARIA) 1.0
A divider that separates and distinguishes sections of content or groups of menuitems.
'[attr.aria-orientation]': 'vertical ? "vertical" : "horizontal"'
Above separator
role supports aria-orientation
which is a state of its orientation. If MatDivider#vertical
property is true, it represents the separator is a vertical separator.
Supported States and Properties | Accessible Rich Internet Applications (WAI-ARIA) 1.0
'[class.mat-divider-vertical]': 'vertical'
It sets mat-divider-vertical
class to <mat-divider>
host element by using class binding. That class is used for styling you can see in divider.scss
https://github.com/angular/components/blob/master/src/material/divider/divider.scss#L10-L14
&.mat-divider-vertical {
border-top: 0;
border-right-width: $mat-divider-width;
border-right-style: solid;
}
'[class.mat-divider-horizontal]': '!vertical'
This line is similar to the above but interestingly mat-divider-horizontal
class is not used in the SCSS. How do you think why it is set?
- https://github.com/angular/components/blob/master/src/material/divider/divider.scss
- https://github.com/angular/components/blob/master/src/material/divider/_divider-theme.scss
As far I can imagine, this is set for user customization. Develoers can override horizontal-specific style by using .mat-divider-hotizontal
. Angular Material supports user-customization at many points.
.my-app {
.mat-divider-horizontal {
border-top-width: 2px; // Override divider's thickness
}
}
To know that philosophy, you can watch a talk by the Angular Material maintainer, Jeremy Elbourn.
'[class.mat-divider-inset]': 'inset'
This sets .mat-divider-inset
class to <mat-divider>
host element.
'class': 'mat-divider'
This sets .mat-divider
class to <mat-divider>
host element. Most of (maybe all?) Angular Material components/directives set its own class to the host element.
template: ''
This component doesn’t has any children but just shows border of the host element.
styleUrls: ['divider.css']
This component has its own style. divider.scss
will be compiled into divider.css
.
encapsulation: ViewEncapsulation.None
Interesting point! Angular Material components basically don’t encapusulate its style. It means styles in divider.css
are exposed to document global.
https://angular.io/api/core/Component#encapsulation
encapusulation
metadata is set to Emulated
by default so we can use safely styles in the component template scope. But scoped styles cannot be overrided from outside even developer. Angular Material explicitly turns off the mechanism to allow user customization.
changeDetection: ChangeDetectionStrategy.OnPush
This component will be re-render only when its any input has been updated.
Component class
Let’s step down into MatDivider
class.
export class MatDivider {
/** Whether the divider is vertically aligned. */
@Input()
get vertical(): boolean { return this._vertical; }
set vertical(value: boolean) { this._vertical = coerceBooleanProperty(value); }
private _vertical: boolean = false;
/** Whether the divider is an inset divider. */
@Input()
get inset(): boolean { return this._inset; }
set inset(value: boolean) { this._inset = coerceBooleanProperty(value); }
private _inset: boolean = false;
static ngAcceptInputType_vertical: BooleanInput;
static ngAcceptInputType_inset: BooleanInput;
}
vertical
Input
MatDivider#vertical
is a set of setter and getter. @Input()
decorator can be placed on setter as well as a normal field.
Angular - Component interaction
inset
Input
Similar to vertical
. :slightly_smiling_face:
static ngAcceptInputType_vertical: BooleanInput;
Interesting point again! This is a special static field for communication with Angular AoT compiler. This ngAcceptInputType_{inputName}
is a hint for type checking the input field with input setter coercion. If you haven’t heard of input setter coercion, read the official document.
https://angular.io/guide/template-typecheck#input-setter-coercion
In short, sometimes an input field needs to accept a value which doesn’t match type. To allow user to write an input shorthand like <mat-divider vertial>
, vertial
setter has to accept ''
in addition to boolean value.
That is whystatic ngAcceptInputType_vertical: BooleanInput;
exists. BooleanInput
is a type provided from @angular/cdk/coercion
. ngAcceptInputType_vertical
tells AoT compier that vertical
input can accept types string | boolean | null | undefined
.
https://github.com/angular/components/blob/master/src/cdk/coercion/boolean-property.ts
export type BooleanInput = string | boolean | null | undefined;
And actual coercion logic is also provided as coerceBooleanProperty
by CDK. So every developers can use the same mechanism in any component.
static ngAcceptInputType_inset: BooleanInput;
Similar to the above.
Summary
-
MatDivider
has aseparator
role. -
MatDivider
provides CSS classes to allow user customization. -
MatDivider
displays only host element border. -
MatDivider
can accept a shorthand of the boolean input like<mat-divider vertical>