docs(component-styles): add chapter about styling components
closes #1047
This commit is contained in:
parent
0fdceec963
commit
6f945b7c38
|
@ -0,0 +1,69 @@
|
||||||
|
describe('Component Style Tests', function () {
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
browser.get('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scopes component styles to component view', function() {
|
||||||
|
var componentH1 = element(by.css('hero-app > h1'));
|
||||||
|
var externalH1 = element(by.css('body > h1'));
|
||||||
|
|
||||||
|
expect(componentH1.getCssValue('fontWeight')).toEqual('normal');
|
||||||
|
expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('allows styling :host element', function() {
|
||||||
|
var host = element(by.css('hero-details'));
|
||||||
|
|
||||||
|
expect(host.getCssValue('borderWidth')).toEqual('1px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports :host() in function form', function() {
|
||||||
|
var host = element(by.css('hero-details'));
|
||||||
|
|
||||||
|
host.element(by.buttonText('Activate')).click();
|
||||||
|
expect(host.getCssValue('borderWidth')).toEqual('3px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows conditional :host-context() styling', function() {
|
||||||
|
var h2 = element(by.css('hero-details h2'));
|
||||||
|
|
||||||
|
expect(h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff
|
||||||
|
});
|
||||||
|
|
||||||
|
it('styles both view and content children with /deep/', function() {
|
||||||
|
var viewH3 = element(by.css('hero-team h3'));
|
||||||
|
var contentH3 = element(by.css('hero-controls h3'));
|
||||||
|
|
||||||
|
expect(viewH3.getCssValue('fontStyle')).toEqual('italic');
|
||||||
|
expect(contentH3.getCssValue('fontStyle')).toEqual('italic');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes styles loaded with CSS @import', function() {
|
||||||
|
var host = element(by.css('hero-details'));
|
||||||
|
|
||||||
|
expect(host.getCssValue('padding')).toEqual('10px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('processes template inline styles', function() {
|
||||||
|
var button = element(by.css('hero-controls button'));
|
||||||
|
var externalButton = element(by.css('body > button'));
|
||||||
|
expect(button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff
|
||||||
|
expect(externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('processes template <link>s', function() {
|
||||||
|
var li = element(by.css('hero-team li:first-child'));
|
||||||
|
var externalLi = element(by.css('body > ul li'));
|
||||||
|
|
||||||
|
expect(li.getCssValue('listStyleType')).toEqual('square');
|
||||||
|
expect(externalLi.getCssValue('listStyleType')).not.toEqual('square');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports relative loading with moduleId', function() {
|
||||||
|
var host = element(by.css('quest-summary'));
|
||||||
|
expect(host.getCssValue('color')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
**/*.js
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {Component, Input} from 'angular2/core';
|
||||||
|
import {Hero} from './hero';
|
||||||
|
import {HeroDetailsComponent} from './hero-details.component';
|
||||||
|
import {HeroControlsComponent} from './hero-controls.component';
|
||||||
|
import {QuestSummaryComponent} from './quest-summary.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-app-main',
|
||||||
|
template: `
|
||||||
|
<quest-summary></quest-summary>
|
||||||
|
<hero-details [hero]=hero [class.active]=hero.active>
|
||||||
|
<hero-controls [hero]=hero></hero-controls>
|
||||||
|
</hero-details>
|
||||||
|
`,
|
||||||
|
directives: [HeroDetailsComponent, HeroControlsComponent, QuestSummaryComponent]
|
||||||
|
})
|
||||||
|
export class HeroAppMainComponent {
|
||||||
|
@Input() hero: Hero;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {Component, HostBinding} from 'angular2/core';
|
||||||
|
import {Hero} from './hero';
|
||||||
|
import {HeroAppMainComponent} from './hero-app-main.component';
|
||||||
|
|
||||||
|
// #docregion
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-app',
|
||||||
|
template: `
|
||||||
|
<h1>Tour of Heroes</h1>
|
||||||
|
<hero-app-main [hero]=hero></hero-app-main>`,
|
||||||
|
styles: ['h1 { font-weight: normal; }'],
|
||||||
|
directives: [HeroAppMainComponent]
|
||||||
|
})
|
||||||
|
// #enddocregion
|
||||||
|
export class HeroAppComponent {
|
||||||
|
hero = new Hero(
|
||||||
|
'Human Torch',
|
||||||
|
['Mister Fantastic', 'Invisible Woman', 'Thing']
|
||||||
|
)
|
||||||
|
|
||||||
|
@HostBinding('class') get themeClass() {
|
||||||
|
return 'theme-light';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import {Component, Input} from 'angular2/core';
|
||||||
|
import {Hero} from './hero';
|
||||||
|
|
||||||
|
// #docregion inlinestyles
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-controls',
|
||||||
|
template: `
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #777;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h3>Controls</h3>
|
||||||
|
<button (click)="activate()">Activate</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
// #enddocregion inlinestyles
|
||||||
|
export class HeroControlsComponent {
|
||||||
|
|
||||||
|
@Input() hero: Hero;
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
this.hero.active = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
:host {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* #docregion import */
|
||||||
|
@import 'hero-details-box.css';
|
||||||
|
/* #enddocregion import */
|
||||||
|
|
||||||
|
/* #docregion host */
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
/* #enddocregion host */
|
||||||
|
|
||||||
|
/* #docregion hostfunction */
|
||||||
|
:host(.active) {
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
/* #enddocregion hostfunction */
|
||||||
|
|
||||||
|
/* #docregion hostcontext */
|
||||||
|
:host-context(.theme-light) h2 {
|
||||||
|
background-color: #eef;
|
||||||
|
}
|
||||||
|
/* #enddocregion hostcontext */
|
||||||
|
|
||||||
|
/* #docregion deep */
|
||||||
|
:host /deep/ h3 {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/* #enddocregion deep */
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {Component, Input} from 'angular2/core';
|
||||||
|
import {Hero} from './hero';
|
||||||
|
import {HeroTeamComponent} from './hero-team.component';
|
||||||
|
|
||||||
|
// #docregion styleurls
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-details',
|
||||||
|
template: `
|
||||||
|
<h2>{{hero.name}}</h2>
|
||||||
|
<hero-team [hero]=hero></hero-team>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
`,
|
||||||
|
styleUrls: ['app/hero-details.component.css'],
|
||||||
|
directives: [HeroTeamComponent]
|
||||||
|
})
|
||||||
|
export class HeroDetailsComponent {
|
||||||
|
// #enddocregion styleurls
|
||||||
|
|
||||||
|
@Input() hero:Hero;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
li {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {Component, Input} from 'angular2/core';
|
||||||
|
import {Hero} from './hero';
|
||||||
|
|
||||||
|
// #docregion stylelink
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-team',
|
||||||
|
template: `
|
||||||
|
<link rel="stylesheet" href="app/hero-team.component.css">
|
||||||
|
<h3>Team</h3>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="#member of hero.team">
|
||||||
|
{{member}}
|
||||||
|
</li>
|
||||||
|
</ul>`
|
||||||
|
})
|
||||||
|
// #enddocregion stylelink
|
||||||
|
export class HeroTeamComponent {
|
||||||
|
@Input() hero: Hero;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export class Hero {
|
||||||
|
active:boolean;
|
||||||
|
|
||||||
|
constructor(public name:string,
|
||||||
|
public team:string[]) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import {bootstrap} from 'angular2/platform/browser';
|
||||||
|
import {HeroAppComponent} from './hero-app.component';
|
||||||
|
|
||||||
|
bootstrap(HeroAppComponent);
|
|
@ -0,0 +1,5 @@
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
background-color: green;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
No quests in progress
|
|
@ -0,0 +1,32 @@
|
||||||
|
// #docplaster
|
||||||
|
import {Component, ViewEncapsulation} from 'angular2/core';
|
||||||
|
|
||||||
|
// #docregion
|
||||||
|
// Let TypeScript know about the special SystemJS __moduleName variable
|
||||||
|
declare var __moduleName: string;
|
||||||
|
// #enddocregion
|
||||||
|
// moduleName is not set in some module loaders; set it explicitly
|
||||||
|
if (!__moduleName) {
|
||||||
|
__moduleName = `http://${location.host}/${location.pathname}/app/`;
|
||||||
|
}
|
||||||
|
console.log(`The __moduleName is ${__moduleName} `);
|
||||||
|
// #docregion
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: __moduleName,
|
||||||
|
selector: 'quest-summary',
|
||||||
|
// #docregion urls
|
||||||
|
templateUrl: 'quest-summary.component.html',
|
||||||
|
styleUrls: ['quest-summary.component.css']
|
||||||
|
// #enddocregion urls
|
||||||
|
// #enddocregion
|
||||||
|
/*
|
||||||
|
// #docregion encapsulation.native
|
||||||
|
// warning: few browsers support shadow DOM encapsulation at this time
|
||||||
|
encapsulation: ViewEncapsulation.Native
|
||||||
|
// #enddocregion encapsulation.native
|
||||||
|
*/
|
||||||
|
// #docregion
|
||||||
|
})
|
||||||
|
export class QuestSummaryComponent { }
|
||||||
|
// #enddocregion
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Component Styles</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
|
||||||
|
<!-- IE required polyfills, in this exact order -->
|
||||||
|
<script src="../node_modules/es6-shim/es6-shim.min.js"></script>
|
||||||
|
<script src="../node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||||
|
<script src="../node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||||
|
|
||||||
|
<script src="../node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||||
|
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="../node_modules/rxjs/bundles/Rx.js"></script>
|
||||||
|
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
<script>
|
||||||
|
System.config({
|
||||||
|
packages: {
|
||||||
|
app: {
|
||||||
|
format: 'register',
|
||||||
|
defaultExtension: 'js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
System.import('app/main')
|
||||||
|
.then(null, console.error.bind(console));
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1 style="visibility: hidden;">External H1 Title for E2E test</h1>
|
||||||
|
<hero-app></hero-app>
|
||||||
|
<button style="visibility: hidden;">External button for E2E test</button>
|
||||||
|
<ul style="visibility: hidden;">
|
||||||
|
<li>External list for E2E test</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"description": "Component Styles",
|
||||||
|
"files": [
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js",
|
||||||
|
"!**/*.native.*"
|
||||||
|
],
|
||||||
|
"tags": ["CSS"]
|
||||||
|
}
|
|
@ -62,6 +62,11 @@
|
||||||
"intro": "Attribute directives attach behavior to elements."
|
"intro": "Attribute directives attach behavior to elements."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"component-styles": {
|
||||||
|
"title": "Component Styles",
|
||||||
|
"intro": "Learn how to apply CSS styles to components."
|
||||||
|
},
|
||||||
|
|
||||||
"hierarchical-dependency-injection": {
|
"hierarchical-dependency-injection": {
|
||||||
"title": "Hierarchical Dependency Injectors",
|
"title": "Hierarchical Dependency Injectors",
|
||||||
"navTitle": "Hierarchical Injectors",
|
"navTitle": "Hierarchical Injectors",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
!= partial("../../../_includes/_ts-temp")
|
|
@ -62,6 +62,11 @@
|
||||||
"intro": "Attribute directives attach behavior to elements."
|
"intro": "Attribute directives attach behavior to elements."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"component-styles": {
|
||||||
|
"title": "Component Styles",
|
||||||
|
"intro": "Learn how to apply CSS styles to components."
|
||||||
|
},
|
||||||
|
|
||||||
"hierarchical-dependency-injection": {
|
"hierarchical-dependency-injection": {
|
||||||
"title": "Hierarchical Dependency Injectors",
|
"title": "Hierarchical Dependency Injectors",
|
||||||
"navTitle": "Hierarchical Injectors",
|
"navTitle": "Hierarchical Injectors",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
!= partial("../../../_includes/_ts-temp")
|
|
@ -62,6 +62,11 @@
|
||||||
"intro": "Attribute directives attach behavior to elements."
|
"intro": "Attribute directives attach behavior to elements."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"component-styles": {
|
||||||
|
"title": "Component Styles",
|
||||||
|
"intro": "Learn how to apply CSS styles to components."
|
||||||
|
},
|
||||||
|
|
||||||
"hierarchical-dependency-injection": {
|
"hierarchical-dependency-injection": {
|
||||||
"title": "Hierarchical Dependency Injectors",
|
"title": "Hierarchical Dependency Injectors",
|
||||||
"navTitle": "Hierarchical Injectors",
|
"navTitle": "Hierarchical Injectors",
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
include ../_util-fns
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Angular 2 applications are styled with regular CSS. That means we can apply
|
||||||
|
everything we know about CSS stylesheets, selectors, rules, and media queries
|
||||||
|
to our Angular applications directly.
|
||||||
|
|
||||||
|
On top of this, Angular has the ability to bundle *component styles*
|
||||||
|
with our components enabling a more modular design than regular stylesheets.
|
||||||
|
|
||||||
|
In this chapter we how to load and apply these *component styles*.
|
||||||
|
|
||||||
|
# Table Of Contents
|
||||||
|
|
||||||
|
* [Using Component Styles](#using-component-styles)
|
||||||
|
* [Special selectors](#special-selectors)
|
||||||
|
* [Loading Styles into Components](#loading-style)
|
||||||
|
* [Controlling View Encapsulation: Emulated, Native, and None](#controlling-view-encapsulation-native-emulated-and-none)
|
||||||
|
* [Appendix 1: Inspecting the generated runtime component styles](#inspect-generated-css)
|
||||||
|
* [Appendix 2: Loading Styles with Relative URLs](#relative-urls)
|
||||||
|
|
||||||
|
**[Run the live code](/resources/live-examples/component-styles/ts/plnkr.html)
|
||||||
|
shown in this chapter.**
|
||||||
|
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Using Component Styles
|
||||||
|
|
||||||
|
For every Angular 2 component we write, we may define not only an HTML template,
|
||||||
|
but also the CSS styles that go with that template,
|
||||||
|
specifying any selectors, rules, and media queries that we need.
|
||||||
|
|
||||||
|
One way to do this is to set the `styles` property in the component metadata.
|
||||||
|
The `styles` property takes an array of strings that contain CSS code.
|
||||||
|
Usually we give it one string as in this example:
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-app.component.ts')(format='.')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Component styles differ from traditional, global styles in a couple of ways.
|
||||||
|
|
||||||
|
Firstly, the selectors we put into a component's styles *only apply withing the template
|
||||||
|
of that component*. The `h1 { }` selector in the example above only applies to the `<h1>` tag
|
||||||
|
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
|
||||||
|
the application are unaffected.
|
||||||
|
|
||||||
|
This is a big improvement in modularity compared to how CSS traditionally works:
|
||||||
|
|
||||||
|
1. We can use the CSS class names and selectors that make the most sense in the context of each component.
|
||||||
|
|
||||||
|
1. Class names and selectors are local to the component and won't collide with
|
||||||
|
classes and selectors used elsewhere in the application.
|
||||||
|
|
||||||
|
1. Our component's styles *cannot* be changed by changes to styles elsewhere in the application.
|
||||||
|
|
||||||
|
1. We can co-locate the CSS code of each component with the TypeScript and HTML code of the component,
|
||||||
|
which leads to a neat and tidy project structure.
|
||||||
|
|
||||||
|
1. We can change or remove component CSS code in the future without trawling through the
|
||||||
|
whole application to see where else it may have been used. We just look at the component we're in.
|
||||||
|
|
||||||
|
a(id="special-selectors")
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Special selectors
|
||||||
|
|
||||||
|
Component styles have a few special *selectors* from the world of
|
||||||
|
<a href="https://www.w3.org/TR/css-scoping-1/" target="_blank">shadow DOM style scoping</a>.
|
||||||
|
|
||||||
|
### :host
|
||||||
|
|
||||||
|
Use the `:host` pseudo-class selector to target styles in the element that *hosts* the component (as opposed to
|
||||||
|
targeting elements *inside* the component's template):
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.css', 'host')(format='.')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
This is the *only* way we can target the host element. We cannot reach
|
||||||
|
it from inside the component with other selectors, because it is not part of the
|
||||||
|
component's own template. It is in a parent component's template.
|
||||||
|
|
||||||
|
Use the *function form* to apply host styles conditionally by
|
||||||
|
including another selector inside parentheses after `:host`.
|
||||||
|
|
||||||
|
In the next example we target the host element again, but only when it also has the `active` CSS class.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.css', 'hostfunction')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### :host-context
|
||||||
|
|
||||||
|
Sometimes it is useful to apply styles based on some condition *outside* a component's view.
|
||||||
|
For example, there may be a CSS theme class applied to the document `<body>` element, and
|
||||||
|
we want to change how our component looks based on that.
|
||||||
|
|
||||||
|
Use the `:host-context()` pseudo-class selector. It works just like the function
|
||||||
|
form of `:host()`. It looks for a CSS class in *any ancestor* of the component host element, all the way
|
||||||
|
up to the document root. It's useful when combined with another selector.
|
||||||
|
|
||||||
|
In the following example, we apply a `background-color` style to all `<h2>` elements *inside* the component, only
|
||||||
|
if some ancestor element has the CSS class `theme-light`.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.css', 'hostcontext')(format='.')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### /deep/
|
||||||
|
|
||||||
|
Component styles normally apply only to the HTML in the component's own template.
|
||||||
|
|
||||||
|
We can use the `/deep/` selector to force a style down through the child component tree into all the child component views.
|
||||||
|
The `/deep/` selector works to any depth of nested components, and it applies *both to the view
|
||||||
|
children and the content children* of the component.
|
||||||
|
|
||||||
|
In this example, we target all `<h3>` elements, from the host element down
|
||||||
|
through this component to all of its child elements in the DOM:
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.css', 'deep')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The `/deep/` selector also has the alias `>>>`. We can use either of the two interchangeably.
|
||||||
|
|
||||||
|
.callout.is-important
|
||||||
|
:marked
|
||||||
|
The `/deep/` and `>>>` selectors should only be used with **emulated** view encapsulation.
|
||||||
|
This is the default and it is what we use most of the time. See the
|
||||||
|
[Controlling View Encapsulation](#controlling-view-encapsulation-native-emulated-and-none)
|
||||||
|
section for more details.
|
||||||
|
|
||||||
|
a(id='loading-styles')
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Loading Styles into Components
|
||||||
|
|
||||||
|
We have several ways to add styles to a component:
|
||||||
|
* inline in the template HTML
|
||||||
|
* by setting `styles` or `styleUrls` metadata
|
||||||
|
* with CSS imports
|
||||||
|
|
||||||
|
The scoping rules outlined above apply to each of these loading patterns.
|
||||||
|
|
||||||
|
### Styles in Metadata
|
||||||
|
|
||||||
|
We can add a `styles` array property to the `@Component` decorator.
|
||||||
|
Each string in the array (usually just one string) defines the css.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-app.component.ts')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Template Inline Styles
|
||||||
|
|
||||||
|
We can embed styles directly into the HTML template by putting them
|
||||||
|
inside `<style>` tags.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-controls.component.ts', 'inlinestyles')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Style URLs in Metadata
|
||||||
|
|
||||||
|
We can load styles from external CSS files by adding a `styleUrls` attribute
|
||||||
|
into a component's `@Component` or `@View` decorator:
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.ts', 'styleurls')
|
||||||
|
|
||||||
|
.callout.is-important
|
||||||
|
:marked
|
||||||
|
The URL is ***relative to the application root*** which is usually the
|
||||||
|
location of the `index.html` web page that hosts the application.
|
||||||
|
The style file URL is *not* relative to the component file.
|
||||||
|
That's why the example URL begins `app/`.
|
||||||
|
See [Appendix 2](#relative-urls) to specify a URL relative to the component file.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Users of module bundlers like Webpack may also use the `styles` attribute to load
|
||||||
|
styles from external files at build time. They could write:
|
||||||
|
|
||||||
|
`styles: [require('my.component.css')]`
|
||||||
|
|
||||||
|
We set the `styles` property, **not** `styleUrls` property! The module bundler is loading the CSS strings, not Angular.
|
||||||
|
Angular only sees the CSS strings *after* the bundler loaded them. To Angular it is as if
|
||||||
|
we wrote the `styles` array by hand.
|
||||||
|
Refer to the module bundler's documentation for information on loading CSS in this manner.
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Template Link Tags
|
||||||
|
|
||||||
|
We can also embed `<link>` tags into the component's HTML template.
|
||||||
|
|
||||||
|
As with `styleUrls`, the link tag's `href` URL is relative to the HTML host page of the application,
|
||||||
|
not relative to the component file.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-team.component.ts', 'stylelink')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### CSS @imports
|
||||||
|
|
||||||
|
We can also import CSS files into our CSS files by using the standard CSS
|
||||||
|
[`@import` rule](https://developer.mozilla.org/en/docs/Web/CSS/@import).
|
||||||
|
|
||||||
|
In *this* case the URL is relative to the CSS file into which we are importing.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/hero-details.component.css', 'import', 'app/hero-details.component.css (excerpt)')
|
||||||
|
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Controlling View Encapsulation: Native, Emulated, and None
|
||||||
|
|
||||||
|
As discussed above, component CSS styles are *encapsulated* into the component's own view and do
|
||||||
|
not affect the rest of the application.
|
||||||
|
|
||||||
|
We can control how this encapsulation happens on a *per
|
||||||
|
component* basis by setting the *view encapsulation mode* in the component metadata. There
|
||||||
|
are three modes to choose from:
|
||||||
|
|
||||||
|
* `Native` view encapsulation uses the browser's native [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||||
|
implementation to attach a Shadow DOM to the component's host element, and then puts the component
|
||||||
|
view inside that Shadow DOM. The component's styles are included within the Shadow DOM.
|
||||||
|
|
||||||
|
* `Emulated` view encapsulation (**the default**) emulates the behavior of Shadow DOM by preprocessing
|
||||||
|
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||||
|
See [Appendix 1](#inspect-generated-css) for details.
|
||||||
|
|
||||||
|
* `None` means that Angular does no view encapsulation.
|
||||||
|
Angular adds the CSS to the global styles.
|
||||||
|
The scoping rules, isolations, and protections discussed earlier do not apply.
|
||||||
|
This is essentially the same as pasting the component's styles into the HTML.
|
||||||
|
|
||||||
|
Set the components encapsulation mode using the `encapsulation` property in the component metadata:
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/quest-summary.component.ts', 'encapsulation.native')(format='.')
|
||||||
|
:marked
|
||||||
|
`Native` view encapsulation only works on [browsers that have native support
|
||||||
|
for Shadow DOM](http://caniuse.com/#feat=shadowdom). The support is still limited,
|
||||||
|
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||||
|
in most cases.
|
||||||
|
|
||||||
|
a(id="inspect-generated-css")
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Appendix 1: Inspecting The CSS Generated in Emulated View Encapsulation
|
||||||
|
|
||||||
|
When using the default emulated view encapsulation, Angular preprocesses
|
||||||
|
all component styles so that they approximate the standard Shadow CSS scoping rules.
|
||||||
|
|
||||||
|
When we inspect the DOM of a running Angular application with emulated view
|
||||||
|
encapsulation enabled, we see that each DOM element has some extra attributes
|
||||||
|
attached to it:
|
||||||
|
|
||||||
|
code-example(format="").
|
||||||
|
<hero-details _nghost-pmm-5>
|
||||||
|
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||||
|
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||||
|
<h3 _ngcontent-pmm-6>Team</h3>
|
||||||
|
</hero-team>
|
||||||
|
</hero-detail>
|
||||||
|
|
||||||
|
:marked
|
||||||
|
We see two kinds of generated attributes:
|
||||||
|
* An element that would be a Shadow DOM host in native encapsulation has a
|
||||||
|
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||||
|
|
||||||
|
* An element within a component's view has a `_ngcontent` attribute
|
||||||
|
that identifies to which host's emulated Shadow DOM this element belongs.
|
||||||
|
|
||||||
|
The exact values of these attributes are not important. They are automatically
|
||||||
|
generated and we never refer to them in application code. But they are targeted
|
||||||
|
by the generated component styles, which we'll find in the `<head>` section of the DOM:
|
||||||
|
|
||||||
|
code-example(format="").
|
||||||
|
[_nghost-pmm-5] {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3[_ngcontent-pmm-6] {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
:marked
|
||||||
|
These are the styles we wrote, post-processed so that each selector is augmented
|
||||||
|
with `_nghost` or `_ngcontent` attribute selectors.
|
||||||
|
These extra selectors enable the scoping rules described in this guide.
|
||||||
|
|
||||||
|
We'll likely live with *emulated* mode until shadow DOM gains traction.
|
||||||
|
|
||||||
|
a(id="relative-urls")
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Appendix 2: Loading Styles with Relative URLs
|
||||||
|
|
||||||
|
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
|
||||||
|
|
||||||
|
code-example(format='').
|
||||||
|
quest-summary.component.ts
|
||||||
|
quest-summary.component.html
|
||||||
|
quest-summary.component.css
|
||||||
|
:marked
|
||||||
|
We include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
||||||
|
Because these files are co-located with the component,
|
||||||
|
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
||||||
|
|
||||||
|
We'd *prefer* to write this:
|
||||||
|
+makeExample('component-styles/ts/app/quest-summary.component.ts', 'urls')(format='.')
|
||||||
|
:marked
|
||||||
|
We can't do that by default. Angular can't find the files and throws an error:
|
||||||
|
|
||||||
|
`EXCEPTION: Failed to load quest-summary.component.html`
|
||||||
|
|
||||||
|
Why can't Angular calculate the HTML and CSS URLs from the component file's location?
|
||||||
|
|
||||||
|
Unfortunately, that location is not readily known.
|
||||||
|
Angular apps can be loaded in many ways: from individual files, from SystemJS bundles, or
|
||||||
|
from CommonJS bundles, to name a few.
|
||||||
|
With this diversity of load strategies, it's not easy to tell at runtime where these files actually reside.
|
||||||
|
|
||||||
|
The only location Angular can be sure of is the URL of the `index.html` home page.
|
||||||
|
So by default it resolves template and style paths relative to the URL of `index.html`.
|
||||||
|
That's why we previously wrote our CSS file URLs with an `app/` base path prefix.
|
||||||
|
|
||||||
|
Although this works with any code loading scheme, it is very inconvenient.
|
||||||
|
We move file folders around all the time during the evolution of our applications.
|
||||||
|
It's no fun patching the style and template URLs when we do.
|
||||||
|
|
||||||
|
### *moduleId*
|
||||||
|
|
||||||
|
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property.
|
||||||
|
|
||||||
|
If we knew the component file's base path, we'd set `moduleId` to that and
|
||||||
|
let Angular construct the full URL from this base path plus the CSS and template file names.
|
||||||
|
|
||||||
|
Our challenge is to calculate the base path with minimal effort.
|
||||||
|
If it's too hard, we shouldn't bother; we should just write the full path to the root and move on.
|
||||||
|
|
||||||
|
Fortunately, *certain* module loaders make it easy.
|
||||||
|
SystemJS (starting in v.0.19.19) sets a special `__moduleName` variable to the URL of the component file.
|
||||||
|
Now it's trivial to set the `moduleId` to the `__moduleName` and write module-relative paths for style and template URLs.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/app/quest-summary.component.ts','', 'app/quest-summary.component.ts')
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
With a module bundler like Webpack we are more likely to set the `styles` and `template` properties with the bundler's
|
||||||
|
`require` mechanism rather than bother with `styleUrls` and `templateUrl`.
|
Loading…
Reference in New Issue