From 6f945b7c387f5e25986ac3e51de635e85ac93bb0 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Tue, 5 Apr 2016 09:27:10 +0300 Subject: [PATCH] docs(component-styles): add chapter about styling components closes #1047 --- .../_examples/component-styles/e2e-spec.js | 69 ++++ .../_examples/component-styles/ts/.gitignore | 1 + .../ts/app/hero-app-main.component.ts | 19 + .../ts/app/hero-app.component.ts | 25 ++ .../ts/app/hero-controls.component.ts | 26 ++ .../ts/app/hero-details-box.css | 3 + .../ts/app/hero-details.component.css | 28 ++ .../ts/app/hero-details.component.ts | 20 + .../ts/app/hero-team.component.css | 3 + .../ts/app/hero-team.component.ts | 19 + .../_examples/component-styles/ts/app/hero.ts | 7 + .../_examples/component-styles/ts/app/main.ts | 4 + .../ts/app/quest-summary.component.css | 5 + .../ts/app/quest-summary.component.html | 1 + .../ts/app/quest-summary.component.ts | 32 ++ .../component-styles/ts/example-config.json | 0 .../_examples/component-styles/ts/index.html | 40 ++ .../_examples/component-styles/ts/plnkr.json | 9 + public/docs/dart/latest/guide/_data.json | 5 + .../dart/latest/guide/component-styles.jade | 1 + public/docs/js/latest/guide/_data.json | 5 + .../js/latest/guide/component-styles.jade | 1 + public/docs/ts/latest/guide/_data.json | 9 +- .../ts/latest/guide/component-styles.jade | 343 ++++++++++++++++++ 24 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 public/docs/_examples/component-styles/e2e-spec.js create mode 100644 public/docs/_examples/component-styles/ts/.gitignore create mode 100644 public/docs/_examples/component-styles/ts/app/hero-app-main.component.ts create mode 100644 public/docs/_examples/component-styles/ts/app/hero-app.component.ts create mode 100644 public/docs/_examples/component-styles/ts/app/hero-controls.component.ts create mode 100644 public/docs/_examples/component-styles/ts/app/hero-details-box.css create mode 100644 public/docs/_examples/component-styles/ts/app/hero-details.component.css create mode 100644 public/docs/_examples/component-styles/ts/app/hero-details.component.ts create mode 100644 public/docs/_examples/component-styles/ts/app/hero-team.component.css create mode 100644 public/docs/_examples/component-styles/ts/app/hero-team.component.ts create mode 100644 public/docs/_examples/component-styles/ts/app/hero.ts create mode 100644 public/docs/_examples/component-styles/ts/app/main.ts create mode 100644 public/docs/_examples/component-styles/ts/app/quest-summary.component.css create mode 100644 public/docs/_examples/component-styles/ts/app/quest-summary.component.html create mode 100644 public/docs/_examples/component-styles/ts/app/quest-summary.component.ts create mode 100644 public/docs/_examples/component-styles/ts/example-config.json create mode 100644 public/docs/_examples/component-styles/ts/index.html create mode 100644 public/docs/_examples/component-styles/ts/plnkr.json create mode 100644 public/docs/dart/latest/guide/component-styles.jade create mode 100644 public/docs/js/latest/guide/component-styles.jade create mode 100644 public/docs/ts/latest/guide/component-styles.jade diff --git a/public/docs/_examples/component-styles/e2e-spec.js b/public/docs/_examples/component-styles/e2e-spec.js new file mode 100644 index 0000000000..e667260f99 --- /dev/null +++ b/public/docs/_examples/component-styles/e2e-spec.js @@ -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 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 + }); + +}); diff --git a/public/docs/_examples/component-styles/ts/.gitignore b/public/docs/_examples/component-styles/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/public/docs/_examples/component-styles/ts/app/hero-app-main.component.ts b/public/docs/_examples/component-styles/ts/app/hero-app-main.component.ts new file mode 100644 index 0000000000..b127a95b5e --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-app-main.component.ts @@ -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: ` + + + + + `, + directives: [HeroDetailsComponent, HeroControlsComponent, QuestSummaryComponent] +}) +export class HeroAppMainComponent { + @Input() hero: Hero; +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-app.component.ts b/public/docs/_examples/component-styles/ts/app/hero-app.component.ts new file mode 100644 index 0000000000..8eb5c9116c --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-app.component.ts @@ -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: ` +

Tour of Heroes

+ `, + 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'; + } + +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-controls.component.ts b/public/docs/_examples/component-styles/ts/app/hero-controls.component.ts new file mode 100644 index 0000000000..fb674d0a63 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-controls.component.ts @@ -0,0 +1,26 @@ +import {Component, Input} from 'angular2/core'; +import {Hero} from './hero'; + +// #docregion inlinestyles +@Component({ + selector: 'hero-controls', + template: ` + +

Controls

+ + ` +}) +// #enddocregion inlinestyles +export class HeroControlsComponent { + + @Input() hero: Hero; + + activate() { + this.hero.active = true; + } +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-details-box.css b/public/docs/_examples/component-styles/ts/app/hero-details-box.css new file mode 100644 index 0000000000..f240073005 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-details-box.css @@ -0,0 +1,3 @@ +:host { + padding: 10px; +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-details.component.css b/public/docs/_examples/component-styles/ts/app/hero-details.component.css new file mode 100644 index 0000000000..fd938ca55c --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-details.component.css @@ -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 */ diff --git a/public/docs/_examples/component-styles/ts/app/hero-details.component.ts b/public/docs/_examples/component-styles/ts/app/hero-details.component.ts new file mode 100644 index 0000000000..344063a543 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-details.component.ts @@ -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: ` +

{{hero.name}}

+ + + `, + styleUrls: ['app/hero-details.component.css'], + directives: [HeroTeamComponent] +}) +export class HeroDetailsComponent { +// #enddocregion styleurls + + @Input() hero:Hero; +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-team.component.css b/public/docs/_examples/component-styles/ts/app/hero-team.component.css new file mode 100644 index 0000000000..b87679886b --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-team.component.css @@ -0,0 +1,3 @@ +li { + list-style-type: square; +} diff --git a/public/docs/_examples/component-styles/ts/app/hero-team.component.ts b/public/docs/_examples/component-styles/ts/app/hero-team.component.ts new file mode 100644 index 0000000000..78885ff1ea --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero-team.component.ts @@ -0,0 +1,19 @@ +import {Component, Input} from 'angular2/core'; +import {Hero} from './hero'; + +// #docregion stylelink +@Component({ + selector: 'hero-team', + template: ` + +

Team

+ ` +}) +// #enddocregion stylelink +export class HeroTeamComponent { + @Input() hero: Hero; +} diff --git a/public/docs/_examples/component-styles/ts/app/hero.ts b/public/docs/_examples/component-styles/ts/app/hero.ts new file mode 100644 index 0000000000..8b58c8a8f8 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/hero.ts @@ -0,0 +1,7 @@ +export class Hero { + active:boolean; + + constructor(public name:string, + public team:string[]) { + } +} diff --git a/public/docs/_examples/component-styles/ts/app/main.ts b/public/docs/_examples/component-styles/ts/app/main.ts new file mode 100644 index 0000000000..a9194f16f3 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/main.ts @@ -0,0 +1,4 @@ +import {bootstrap} from 'angular2/platform/browser'; +import {HeroAppComponent} from './hero-app.component'; + +bootstrap(HeroAppComponent); diff --git a/public/docs/_examples/component-styles/ts/app/quest-summary.component.css b/public/docs/_examples/component-styles/ts/app/quest-summary.component.css new file mode 100644 index 0000000000..207fa981dd --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/quest-summary.component.css @@ -0,0 +1,5 @@ +:host { + display: block; + background-color: green; + color: white; +} diff --git a/public/docs/_examples/component-styles/ts/app/quest-summary.component.html b/public/docs/_examples/component-styles/ts/app/quest-summary.component.html new file mode 100644 index 0000000000..abf63c2542 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/quest-summary.component.html @@ -0,0 +1 @@ +No quests in progress diff --git a/public/docs/_examples/component-styles/ts/app/quest-summary.component.ts b/public/docs/_examples/component-styles/ts/app/quest-summary.component.ts new file mode 100644 index 0000000000..daea1a26b9 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/app/quest-summary.component.ts @@ -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 diff --git a/public/docs/_examples/component-styles/ts/example-config.json b/public/docs/_examples/component-styles/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/component-styles/ts/index.html b/public/docs/_examples/component-styles/ts/index.html new file mode 100644 index 0000000000..58098522ff --- /dev/null +++ b/public/docs/_examples/component-styles/ts/index.html @@ -0,0 +1,40 @@ + + + + Component Styles + + + + + + + + + + + + + + + + +

External H1 Title for E2E test

+ + + + + + diff --git a/public/docs/_examples/component-styles/ts/plnkr.json b/public/docs/_examples/component-styles/ts/plnkr.json new file mode 100644 index 0000000000..fb313556fc --- /dev/null +++ b/public/docs/_examples/component-styles/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Component Styles", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.native.*" + ], + "tags": ["CSS"] +} diff --git a/public/docs/dart/latest/guide/_data.json b/public/docs/dart/latest/guide/_data.json index b87d2f86e9..fca35dcc06 100644 --- a/public/docs/dart/latest/guide/_data.json +++ b/public/docs/dart/latest/guide/_data.json @@ -62,6 +62,11 @@ "intro": "Attribute directives attach behavior to elements." }, + "component-styles": { + "title": "Component Styles", + "intro": "Learn how to apply CSS styles to components." + }, + "hierarchical-dependency-injection": { "title": "Hierarchical Dependency Injectors", "navTitle": "Hierarchical Injectors", diff --git a/public/docs/dart/latest/guide/component-styles.jade b/public/docs/dart/latest/guide/component-styles.jade new file mode 100644 index 0000000000..f8df2a84a6 --- /dev/null +++ b/public/docs/dart/latest/guide/component-styles.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") \ No newline at end of file diff --git a/public/docs/js/latest/guide/_data.json b/public/docs/js/latest/guide/_data.json index 335b1f562b..f92809bc9e 100644 --- a/public/docs/js/latest/guide/_data.json +++ b/public/docs/js/latest/guide/_data.json @@ -62,6 +62,11 @@ "intro": "Attribute directives attach behavior to elements." }, + "component-styles": { + "title": "Component Styles", + "intro": "Learn how to apply CSS styles to components." + }, + "hierarchical-dependency-injection": { "title": "Hierarchical Dependency Injectors", "navTitle": "Hierarchical Injectors", diff --git a/public/docs/js/latest/guide/component-styles.jade b/public/docs/js/latest/guide/component-styles.jade new file mode 100644 index 0000000000..f8df2a84a6 --- /dev/null +++ b/public/docs/js/latest/guide/component-styles.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") \ No newline at end of file diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index 3e89d91a23..5366524e6b 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -56,12 +56,17 @@ "nextable": true, "basics": true }, - + "attribute-directives": { "title": "Attribute Directives", "intro": "Attribute directives attach behavior to elements." }, + "component-styles": { + "title": "Component Styles", + "intro": "Learn how to apply CSS styles to components." + }, + "hierarchical-dependency-injection": { "title": "Hierarchical Dependency Injectors", "navTitle": "Hierarchical Injectors", @@ -107,7 +112,7 @@ "title": "TypeScript Configuration", "intro": "TypeScript configuration for Angular 2 developers" }, - + "upgrade": { "title": "Upgrading from 1.x", "intro": "Angular 1 applications can be incrementally upgraded to Angular 2." diff --git a/public/docs/ts/latest/guide/component-styles.jade b/public/docs/ts/latest/guide/component-styles.jade new file mode 100644 index 0000000000..fc6569c954 --- /dev/null +++ b/public/docs/ts/latest/guide/component-styles.jade @@ -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 `

` tag + in the template of `HeroAppComponent`. Any `

` 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 + shadow DOM style scoping. + + ### :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 `` 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 `

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

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