Edits to "Component Styles" and "Pipes" (#3266)
* Edits to "Component Styles" * Edits to "Pipes". * Updated with feedback from Jules and a few minor edits.
This commit is contained in:
parent
bc1b98d822
commit
f1274f68be
|
@ -2,102 +2,98 @@ block includes
|
||||||
include ../_util-fns
|
include ../_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular applications are styled with regular CSS. That means we can apply
|
Angular applications are styled with standard CSS. That means you can apply
|
||||||
everything we know about CSS stylesheets, selectors, rules, and media queries
|
everything you know about CSS stylesheets, selectors, rules, and media queries
|
||||||
to our Angular applications directly.
|
directly to Angular applications.
|
||||||
|
|
||||||
On top of this, Angular has the ability to bundle *component styles*
|
Additionally, Angular can bundle *component styles*
|
||||||
with our components enabling a more modular design than regular stylesheets.
|
with components, enabling a more modular design than regular stylesheets.
|
||||||
|
|
||||||
In this chapter we learn how to load and apply these *component styles*.
|
This page describes how to load and apply these component styles.
|
||||||
|
|
||||||
## Table Of Contents
|
## Table Of Contents
|
||||||
|
|
||||||
* [Using Component Styles](#using-component-styles)
|
* [Using component styles](#using-component-styles)
|
||||||
* [Special selectors](#special-selectors)
|
* [Special selectors](#special-selectors)
|
||||||
* [Loading Styles into Components](#loading-styles)
|
* [Loading styles into components](#loading-styles)
|
||||||
* [Controlling View Encapsulation: Emulated, Native, and None](#view-encapsulation)
|
* [Controlling view encapsulation: native, emulated, and none](#view-encapsulation)
|
||||||
* [Appendix 1: Inspecting the generated runtime component styles](#inspect-generated-css)
|
* [Appendix 1: Inspecting the CSS generated in emulated view encapsulation](#inspect-generated-css)
|
||||||
* [Appendix 2: Loading Styles with Relative URLs](#relative-urls)
|
* [Appendix 2: Loading styles with relative URLs](#relative-urls)
|
||||||
|
|
||||||
Run the <live-example></live-example> of the code shown in this chapter.
|
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Using Component Styles
|
## Using component styles
|
||||||
|
|
||||||
For every Angular component we write, we may define not only an HTML template,
|
For every Angular component you write, you may define not only an HTML template,
|
||||||
but also the CSS styles that go with that template,
|
but also the CSS styles that go with that template,
|
||||||
specifying any selectors, rules, and media queries that we need.
|
specifying any selectors, rules, and media queries that you need.
|
||||||
|
|
||||||
One way to do this is to set the `styles` property in the component metadata.
|
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.
|
The `styles` property takes #{_an} #{_array} of strings that contain CSS code.
|
||||||
Usually we give it one string as in this example:
|
Usually you give it one string, as in the following example:
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
|
+makeExample('component-styles/ts/src/app/hero-app.component.ts')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Component styles differ from traditional, global styles in a couple of ways.
|
The selectors you put into a component's styles apply only within the template
|
||||||
|
of that component. The `h1` selector in the preceding example applies only to the `<h1>` tag
|
||||||
Firstly, the selectors we put into a component's styles *only apply within 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
|
in the template of `HeroAppComponent`. Any `<h1>` elements elsewhere in
|
||||||
the application are unaffected.
|
the application are unaffected.
|
||||||
|
|
||||||
This is a big improvement in modularity compared to how CSS traditionally works:
|
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.
|
* You can use the CSS class names and selectors that make the most sense in the context of each component.
|
||||||
|
* Class names and selectors are local to the component and don't collide with
|
||||||
1. Class names and selectors are local to the component and won't collide with
|
|
||||||
classes and selectors used elsewhere in the application.
|
classes and selectors used elsewhere in the application.
|
||||||
|
* Changes to styles elsewhere in the application don't affect the component's styles.
|
||||||
1. Our component's styles *cannot* be changed by changes to styles elsewhere in the application.
|
* You can co-locate the CSS code of each component with the TypeScript and HTML code of the component,
|
||||||
|
|
||||||
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.
|
which leads to a neat and tidy project structure.
|
||||||
|
* You can change or remove component CSS code without searching through the
|
||||||
1. We can change or remove component CSS code in the future without trawling through the
|
whole application to find where else the code is used.
|
||||||
whole application to see where else it may have been used. We just look at the component we're in.
|
|
||||||
|
|
||||||
a(id="special-selectors")
|
a(id="special-selectors")
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Special selectors
|
## Special selectors
|
||||||
|
|
||||||
Component styles have a few special *selectors* from the world of
|
Component styles have a few special *selectors* from the world of shadow DOM style scoping
|
||||||
[shadow DOM style scoping](https://www.w3.org/TR/css-scoping-1):
|
(described in the [CSS Scoping Module Level 1](https://www.w3.org/TR/css-scoping-1) page on the
|
||||||
|
[W3C](https://www.w3.org) site).
|
||||||
|
The following sections describe these selectors.
|
||||||
|
|
||||||
### :host
|
### :host
|
||||||
|
|
||||||
Use the `:host` pseudo-class selector to target styles in the element that *hosts* the component (as opposed to
|
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):
|
targeting elements *inside* the component's template).
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'host')(format='.')
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'host')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This is the *only* way we can target the host element. We cannot reach
|
The `:host` selector is the only way to target the host element. You can't reach
|
||||||
it from inside the component with other selectors, because it is not part of the
|
the host element from inside the component with other selectors because it's not part of the
|
||||||
component's own template. It is in a parent component's template.
|
component's own template. The host element is in a parent component's template.
|
||||||
|
|
||||||
Use the *function form* to apply host styles conditionally by
|
Use the *function form* to apply host styles conditionally by
|
||||||
including another selector inside parentheses after `:host`.
|
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.
|
The next example targets the host element again, but only when it also has the `active` CSS class.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'hostfunction')(format=".")
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'hostfunction')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### :host-context
|
### :host-context
|
||||||
|
|
||||||
Sometimes it is useful to apply styles based on some condition *outside* a component's view.
|
Sometimes it's useful to apply styles based on some condition *outside* of a component's view.
|
||||||
For example, there may be a CSS theme class applied to the document `<body>` element, and
|
For example, a CSS theme class could be applied to the document `<body>` element, and
|
||||||
we want to change how our component looks based on that.
|
you want to change how your component looks based on that.
|
||||||
|
|
||||||
Use the `:host-context()` pseudo-class selector. It works just like the function
|
Use the `:host-context()` pseudo-class selector, which 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
|
form of `:host()`. The `:host-context()` selector looks for a CSS class in any ancestor of the component host element,
|
||||||
up to the document root. It's useful when combined with another selector.
|
up to the document root. The `:host-context()` selector is useful when combined with another selector.
|
||||||
|
|
||||||
In the following example, we apply a `background-color` style to all `<h2>` elements *inside* the component, only
|
The following example applies a `background-color` style to all `<h2>` elements *inside* the component, only
|
||||||
if some ancestor element has the CSS class `theme-light`.
|
if some ancestor element has the CSS class `theme-light`.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'hostcontext')(format='.')
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'hostcontext')(format='.')
|
||||||
|
@ -107,55 +103,46 @@ a(id="special-selectors")
|
||||||
|
|
||||||
Component styles normally apply only to the HTML in the component's own template.
|
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.
|
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
|
The `/deep/` selector works to any depth of nested components, and it applies to both the view
|
||||||
children and the content children* of the component.
|
children and content children of the component.
|
||||||
|
|
||||||
In this example, we target all `<h3>` elements, from the host element down
|
The following example targets all `<h3>` elements, from the host element down
|
||||||
through this component to all of its child elements in the DOM:
|
through this component to all of its child elements in the DOM.
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'deep')(format=".")
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'deep')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `/deep/` selector also has the alias `>>>`. We can use either of the two interchangeably.
|
The `/deep/` selector also has the alias `>>>`. You can use either interchangeably.
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
The `/deep/` and `>>>` selectors should only be used with **emulated** view encapsulation.
|
Use the `/deep/` and `>>>` selectors only with *emulated* view encapsulation.
|
||||||
This is the default and it is what we use most of the time. See the
|
Emulated is the default and most commonly used view encapsulation. For more information, see the
|
||||||
[Controlling View Encapsulation](#view-encapsulation)
|
[Controlling view encapsulation](#view-encapsulation) section.
|
||||||
section for more details.
|
|
||||||
|
|
||||||
a(id='loading-styles')
|
a(id='loading-styles')
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Loading Styles into Components
|
## Loading styles into components
|
||||||
|
|
||||||
We have several ways to add styles to a component:
|
There are several ways to add styles to a component:
|
||||||
* inline in the template HTML
|
* By setting `styles` or `styleUrls` metadata.
|
||||||
* by setting `styles` or `styleUrls` metadata
|
* Inline in the template HTML.
|
||||||
* with CSS imports
|
* With CSS imports.
|
||||||
|
|
||||||
The scoping rules outlined above apply to each of these loading patterns.
|
The scoping rules outlined earlier apply to each of these loading patterns.
|
||||||
|
|
||||||
### Styles in Metadata
|
### Styles in metadata
|
||||||
|
|
||||||
We can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
You can add a `styles` #{_array} property to the `@Component` #{_decorator}.
|
||||||
Each string in the #{_array} (usually just one string) defines the CSS.
|
Each string in the #{_array} (usually just one string) defines the CSS.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
|
+makeExample('component-styles/ts/src/app/hero-app.component.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Template Inline Styles
|
### Style URLs in metadata
|
||||||
|
|
||||||
We can embed styles directly into the HTML template by putting them
|
You can load styles from external CSS files by adding a `styleUrls` attribute
|
||||||
inside `<style>` tags.
|
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/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` #{_decorator}:
|
into a component's `@Component` #{_decorator}:
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
|
+makeExample('component-styles/ts/src/app/hero-details.component.ts', 'styleurls')
|
||||||
|
@ -163,95 +150,103 @@ a(id='loading-styles')
|
||||||
block style-url
|
block style-url
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:marked
|
:marked
|
||||||
The URL is ***relative to the application root*** which is usually the
|
The URL is relative to the *application root*, which is usually the
|
||||||
location of the `index.html` web page that hosts the application.
|
location of the `index.html` web page that hosts the application.
|
||||||
The style file URL is *not* relative to the component file.
|
The style file URL is *not* relative to the component file.
|
||||||
That's why the example URL begins `src/app/`.
|
That's why the example URL begins `src/app/`.
|
||||||
See [Appendix 2](#relative-urls) to specify a URL relative to the
|
To specify a URL relative to the component file, see [Appendix 2](#relative-urls).
|
||||||
component file.
|
|
||||||
|
|
||||||
block module-bundlers
|
block module-bundlers
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Users of module bundlers like Webpack may also use the `styles` attribute
|
If you use module bundlers like Webpack, you can also use the `styles` attribute
|
||||||
to load styles from external files at build time. They could write:
|
to load styles from external files at build time. You could write:
|
||||||
|
|
||||||
`styles: [require('my.component.css')]`
|
`styles: [require('my.component.css')]`
|
||||||
|
|
||||||
We set the `styles` property, **not** `styleUrls` property! The module
|
Set the `styles` property, not the `styleUrls` property. The module
|
||||||
bundler is loading the CSS strings, not Angular.
|
bundler loads the CSS strings, not Angular.
|
||||||
Angular only sees the CSS strings *after* the bundler loads them.
|
Angular sees the CSS strings only after the bundler loads them.
|
||||||
To Angular it is as if we wrote the `styles` array by hand.
|
To Angular, it's as if you wrote the `styles` array by hand.
|
||||||
Refer to the module bundler's documentation for information on
|
For information on loading CSS in this manner, refer to the module bundler's documentation.
|
||||||
loading CSS in this manner.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Template Link Tags
|
### Template inline styles
|
||||||
|
|
||||||
We can also embed `<link>` tags into the component's HTML template.
|
You can embed styles directly into the HTML template by putting them
|
||||||
|
inside `<style>` tags.
|
||||||
|
|
||||||
|
+makeExample('component-styles/ts/src/app/hero-controls.component.ts', 'inlinestyles')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Template link tags
|
||||||
|
|
||||||
|
You can also embed `<link>` tags into the component's HTML template.
|
||||||
|
|
||||||
As with `styleUrls`, the link tag's `href` URL is relative to the
|
As with `styleUrls`, the link tag's `href` URL is relative to the
|
||||||
application root, not relative to the component file.
|
application root, not the component file.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-team.component.ts', 'stylelink')
|
+makeExample('component-styles/ts/src/app/hero-team.component.ts', 'stylelink')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### CSS @imports
|
### CSS @imports
|
||||||
|
|
||||||
We can also import CSS files into our CSS files by using the standard CSS
|
You can also import CSS files into the CSS files using the standard CSS `@import` rule.
|
||||||
[`@import` rule](https://developer.mozilla.org/en/docs/Web/CSS/@import).
|
For details, see [`@import`](https://developer.mozilla.org/en/docs/Web/CSS/@import)
|
||||||
|
on the [MDN](https://developer.mozilla.org) site.
|
||||||
|
|
||||||
block css-import-url
|
block css-import-url
|
||||||
:marked
|
:marked
|
||||||
In *this* case the URL is relative to the CSS file into which we are importing.
|
In this case, the URL is relative to the CSS file into which you're importing.
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
|
+makeExample('component-styles/ts/src/app/hero-details.component.css', 'import', 'src/app/hero-details.component.css (excerpt)')
|
||||||
|
|
||||||
a#view-encapsulation
|
a#view-encapsulation
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Controlling View Encapsulation: Native, Emulated, and None
|
## Controlling view encapsulation: native, emulated, and none
|
||||||
|
|
||||||
As discussed above, component CSS styles are *encapsulated* into the component's own view and do
|
As discussed earlier, component CSS styles are encapsulated into the component's view and don't
|
||||||
not affect the rest of the application.
|
affect the rest of the application.
|
||||||
|
|
||||||
We can control how this encapsulation happens on a *per
|
To control how this encapsulation happens on a *per
|
||||||
component* basis by setting the *view encapsulation mode* in the component metadata. There
|
component* basis, you can set the *view encapsulation mode* in the component metadata.
|
||||||
are three modes to choose from:
|
Choose from the following modes:
|
||||||
|
|
||||||
* `Native` view encapsulation uses the browser's native [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
* `Native` view encapsulation uses the browser's native shadow DOM implementation (see
|
||||||
implementation to attach a Shadow DOM to the component's host element, and then puts the component
|
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||||
view inside that Shadow DOM. The component's styles are included within the Shadow DOM.
|
on the [MDN](https://developer.mozilla.org) site)
|
||||||
|
to attach a shadow DOM to the component's host element, and then puts the component
|
||||||
* `Emulated` view encapsulation (**the default**) emulates the behavior of Shadow DOM by preprocessing
|
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.
|
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||||
See [Appendix 1](#inspect-generated-css) for details.
|
For details, see [Appendix 1](#inspect-generated-css).
|
||||||
|
|
||||||
* `None` means that Angular does no view encapsulation.
|
* `None` means that Angular does no view encapsulation.
|
||||||
Angular adds the CSS to the global styles.
|
Angular adds the CSS to the global styles.
|
||||||
The scoping rules, isolations, and protections discussed earlier do not apply.
|
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||||
This is essentially the same as pasting the component's styles into the HTML.
|
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:
|
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||||
|
|
||||||
+makeExample('component-styles/ts/src/app/quest-summary.component.ts', 'encapsulation.native')(format='.')
|
+makeExample('component-styles/ts/src/app/quest-summary.component.ts', 'encapsulation.native')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
`Native` view encapsulation only works on [browsers that have native support
|
`Native` view encapsulation only works on browsers that have native support
|
||||||
for Shadow DOM](http://caniuse.com/#feat=shadowdom). The support is still limited,
|
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
||||||
|
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||||
in most cases.
|
in most cases.
|
||||||
|
|
||||||
a#inspect-generated-css
|
a#inspect-generated-css
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Appendix 1: Inspecting The CSS Generated in Emulated View Encapsulation
|
## Appendix 1: Inspecting the CSS generated in emulated view encapsulation
|
||||||
|
|
||||||
When using the default emulated view encapsulation, Angular preprocesses
|
When using emulated view encapsulation, Angular preprocesses
|
||||||
all component styles so that they approximate the standard Shadow CSS scoping rules.
|
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
|
In the DOM of a running Angular application with emulated view
|
||||||
encapsulation enabled, we see that each DOM element has some extra attributes
|
encapsulation enabled, each DOM element has some extra attributes
|
||||||
attached to it:
|
attached to it:
|
||||||
|
|
||||||
code-example(format="").
|
code-example(format="").
|
||||||
|
@ -263,16 +258,15 @@ code-example(format="").
|
||||||
</hero-detail>
|
</hero-detail>
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We see two kinds of generated attributes:
|
There are two kinds of generated attributes:
|
||||||
* An element that would be a Shadow DOM host in native encapsulation has a
|
* 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.
|
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||||
|
|
||||||
* An element within a component's view has a `_ngcontent` attribute
|
* An element within a component's view has a `_ngcontent` attribute
|
||||||
that identifies to which host's emulated Shadow DOM this element belongs.
|
that identifies to which host's emulated shadow DOM this element belongs.
|
||||||
|
|
||||||
The exact values of these attributes are not important. They are automatically
|
The exact values of these attributes aren't important. They are automatically
|
||||||
generated and we never refer to them in application code. But they are targeted
|
generated and you 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:
|
by the generated component styles, which are in the `<head>` section of the DOM:
|
||||||
|
|
||||||
code-example(format="").
|
code-example(format="").
|
||||||
[_nghost-pmm-5] {
|
[_nghost-pmm-5] {
|
||||||
|
@ -286,16 +280,14 @@ code-example(format="").
|
||||||
}
|
}
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
These are the styles we wrote, post-processed so that each selector is augmented
|
These styles are post-processed so that each selector is augmented
|
||||||
with `_nghost` or `_ngcontent` attribute selectors.
|
with `_nghost` or `_ngcontent` attribute selectors.
|
||||||
These extra selectors enable the scoping rules described in this guide.
|
These extra selectors enable the scoping rules described in this page.
|
||||||
|
|
||||||
We'll likely live with *emulated* mode until shadow DOM gains traction.
|
|
||||||
|
|
||||||
a#relative-urls
|
a#relative-urls
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Appendix 2: Loading Styles with Relative URLs
|
## 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:
|
It's common practice to split a component's code, HTML, and CSS into three separate files in the same directory:
|
||||||
code-example(format="nocode").
|
code-example(format="nocode").
|
||||||
|
@ -304,16 +296,16 @@ code-example(format="nocode").
|
||||||
quest-summary.component.css
|
quest-summary.component.css
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
You include the template and CSS files by setting the `templateUrl` and `styleUrls` metadata properties respectively.
|
||||||
Because these files are co-located with the component,
|
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.
|
it would be nice to refer to them by name without also having to specify a path back to the root of the application.
|
||||||
|
|
||||||
block module-id
|
block module-id
|
||||||
:marked
|
:marked
|
||||||
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property to `module.id`.
|
You can change the way Angular calculates the full URL by setting the component metadata's `moduleId` property to `module.id`.
|
||||||
|
|
||||||
+makeExample('src/app/quest-summary.component.ts')
|
+makeExample('src/app/quest-summary.component.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Learn more about `moduleId` in the [Component-Relative Paths](../cookbook/component-relative-paths.html) chapter.
|
Learn more about `moduleId` in the [Component-Relative Paths](../cookbook/component-relative-paths.html) page.
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,29 @@ block includes
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
||||||
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
|
Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.
|
||||||
|
|
||||||
Once data arrive, we could push their raw `toString` values directly to the view.
|
Once data arrive, you could push their raw `toString` values directly to the view,
|
||||||
That rarely makes for a good user experience.
|
but that rarely makes for a good user experience.
|
||||||
E.g., almost everyone prefers a simple birthday date like
|
For example, in most use cases, users prefer to see a date in a simple format like
|
||||||
<samp>April 15, 1988</samp> to the original raw string format
|
<samp>April 15, 1988</samp> rather than the raw string format
|
||||||
— <samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>.
|
<samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>.
|
||||||
|
|
||||||
Clearly some values benefit from a bit of massage. We soon discover that we
|
Clearly, some values benefit from a bit of editing. You may notice that you
|
||||||
desire many of the same transformations repeatedly, both within and across many applications.
|
desire many of the same transformations repeatedly, both within and across many applications.
|
||||||
We almost think of them as styles.
|
You can almost think of them as styles.
|
||||||
In fact, we'd like to apply them in our HTML templates as we do styles.
|
In fact, you might like to apply them in your HTML templates as you do styles.
|
||||||
|
|
||||||
Introducing Angular pipes, a way to write display-value transformations that we can declare in our HTML!
|
Introducing Angular pipes, a way to write display-value transformations that you can declare in your HTML.
|
||||||
Try the <live-example></live-example>.
|
|
||||||
|
You can run the <live-example></live-example> in Plunker and download the code from there.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Using Pipes
|
## Using pipes
|
||||||
|
|
||||||
A pipe takes in data as input and transforms it to a desired output.
|
A pipe takes in data as input and transforms it to a desired output.
|
||||||
We'll illustrate by transforming a component's birthday property into
|
In this page, you'll use pipes to transform a component's birthday property into
|
||||||
a human-friendly date.
|
a human-friendly date.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/hero-birthday1.component.ts', null, 'src/app/hero-birthday1.component.ts')(format='.')
|
+makeExample('pipes/ts/src/app/hero-birthday1.component.ts', null, 'src/app/hero-birthday1.component.ts')(format='.')
|
||||||
|
@ -35,14 +36,14 @@ block includes
|
||||||
+makeExample('pipes/ts/src/app/app.component.html', 'hero-birthday-template')(format=".")
|
+makeExample('pipes/ts/src/app/app.component.html', 'hero-birthday-template')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Inside the interpolation expression we flow the component's `birthday` value through the
|
Inside the interpolation expression, you flow the component's `birthday` value through the
|
||||||
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-pipe.html)
|
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/index/DatePipe-pipe.html)
|
||||||
function on the right. All pipes work this way.
|
function on the right. All pipes work this way.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `Date` and `Currency` pipes need the **ECMAScript Internationalization API**.
|
The `Date` and `Currency` pipes need the *ECMAScript Internationalization API*.
|
||||||
Safari and other older browsers don't support it. We can add support with a polyfill.
|
Safari and other older browsers don't support it. You can add support with a polyfill.
|
||||||
|
|
||||||
code-example(language="html").
|
code-example(language="html").
|
||||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
|
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
|
||||||
|
@ -52,48 +53,49 @@ block includes
|
||||||
## Built-in pipes
|
## Built-in pipes
|
||||||
Angular comes with a stock of pipes such as
|
Angular comes with a stock of pipes such as
|
||||||
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
||||||
They are all immediately available for use in any template.
|
They are all available for use in any template.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Learn more about these and many other built-in pipes in the [API Reference](../api/#!?query=pipe);
|
Read more about these and many other built-in pipes in the [pipes topics](../api/#!?query=pipe) of the
|
||||||
filter for entries that include the word "pipe".
|
[API Reference](../api); filter for entries that include the word "pipe".
|
||||||
|
|
||||||
Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in an [appendix below](#no-filter-pipe).
|
Angular doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in the [Appendix](#no-filter-pipe) of this page.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Parameterizing a Pipe
|
## Parameterizing a pipe
|
||||||
|
|
||||||
A pipe may accept any number of optional parameters to fine-tune its output.
|
A pipe can accept any number of optional parameters to fine-tune its output.
|
||||||
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
|
To add parameters to a pipe, follow the pipe name with a colon ( : ) and then the parameter value
|
||||||
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
|
(such as `currency:'EUR'`). If the pipe accepts multiple parameters, separate the values with colons (such as `slice:1:5`)
|
||||||
|
|
||||||
We'll modify our birthday template to give the date pipe a format parameter.
|
Modify the birthday template to give the date pipe a format parameter.
|
||||||
After formatting the hero's April 15th birthday, it should render as **<samp>04/15/88</samp>**:
|
After formatting the hero's April 15th birthday, it renders as **<samp>04/15/88</samp>**:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.component.html', 'format-birthday')(format=".")
|
+makeExample('pipes/ts/src/app/app.component.html', 'format-birthday')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The parameter value can be any valid
|
The parameter value can be any valid template expression,
|
||||||
[template expression](./template-syntax.html#template-expressions)
|
(see the [Template expressions](./template-syntax.html#template-expressions) section of the
|
||||||
|
[Template Syntax](./template-syntax.html) page)
|
||||||
such as a string literal or a component property.
|
such as a string literal or a component property.
|
||||||
In other words, we can control the format through a binding the same way we control the birthday value through a binding.
|
In other words, you can control the format through a binding the same way you control the birthday value through a binding.
|
||||||
|
|
||||||
Let's write a second component that *binds* the pipe's format parameter
|
Write a second component that *binds* the pipe's format parameter
|
||||||
to the component's `format` property. Here's the template for that component:
|
to the component's `format` property. Here's the template for that component:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/hero-birthday2.component.ts', 'template', 'src/app/hero-birthday2.component.ts (template)')(format=".")
|
+makeExample('pipes/ts/src/app/hero-birthday2.component.ts', 'template', 'src/app/hero-birthday2.component.ts (template)')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We also added a button to the template and bound its click event to the component's `toggleFormat()` method.
|
You also added a button to the template and bound its click event to the component's `toggleFormat()` method.
|
||||||
That method toggles the component's `format` property between a short form
|
That method toggles the component's `format` property between a short form
|
||||||
(`'shortDate'`) and a longer form (`'fullDate'`).
|
(`'shortDate'`) and a longer form (`'fullDate'`).
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/hero-birthday2.component.ts', 'class', 'src/app/hero-birthday2.component.ts (class)')(format='.')
|
+makeExample('pipes/ts/src/app/hero-birthday2.component.ts', 'class', 'src/app/hero-birthday2.component.ts (class)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
As we click the button, the displayed date alternates between
|
As you click the button, the displayed date alternates between
|
||||||
"**<samp>04/15/1988</samp>**" and
|
"**<samp>04/15/1988</samp>**" and
|
||||||
"**<samp>Friday, April 15, 1988</samp>**".
|
"**<samp>Friday, April 15, 1988</samp>**".
|
||||||
|
|
||||||
|
@ -103,61 +105,58 @@ figure.image-display
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Learn more about the `DatePipe` format options in the [API Docs](../api/common/index/DatePipe-pipe.html).
|
Read more about the `DatePipe` format options in the [Date Pipe](../api/common/index/DatePipe-pipe.html)
|
||||||
|
API Reference page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Chaining pipes
|
## Chaining pipes
|
||||||
|
|
||||||
We can chain pipes together in potentially useful combinations.
|
You can chain pipes together in potentially useful combinations.
|
||||||
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
|
In the following example, to display the birthday in uppercase,
|
||||||
so we can display the birthday in uppercase. The following birthday displays as
|
the birthday is chained to the `DatePipe` and on to the `UpperCasePipe`.
|
||||||
**<samp>APR 15, 1988</samp>**.
|
The birthday displays as **<samp>APR 15, 1988</samp>**.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.component.html', 'chained-birthday')(format=".")
|
+makeExample('pipes/ts/src/app/app.component.html', 'chained-birthday')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This example — which displays **<samp>FRIDAY, APRIL 15, 1988</samp>** —
|
This example—which displays **<samp>FRIDAY, APRIL 15, 1988</samp>**—chains
|
||||||
chains the same pipes as above, but passes in a parameter to `date` as well.
|
the same pipes as above, but passes in a parameter to `date` as well.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.component.html', 'chained-parameter-birthday')(format=".")
|
+makeExample('pipes/ts/src/app/app.component.html', 'chained-parameter-birthday')(format=".")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Custom Pipes
|
## Custom pipes
|
||||||
|
|
||||||
We can write our own custom pipes.
|
You can write your own custom pipes.
|
||||||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/exponential-strength.pipe.ts', null, 'src/app/exponential-strength.pipe.ts')(format=".")
|
+makeExample('pipes/ts/src/app/exponential-strength.pipe.ts', null, 'src/app/exponential-strength.pipe.ts')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
This pipe definition reveals several key points:
|
This pipe definition reveals the following key points:
|
||||||
|
|
||||||
* A pipe is a class decorated with pipe metadata.
|
* A pipe is a class decorated with pipe metadata.
|
||||||
|
|
||||||
* The pipe class implements the `PipeTransform` interface's `transform` method that
|
* The pipe class implements the `PipeTransform` interface's `transform` method that
|
||||||
accepts an input value followed by optional parameters and returns the transformed value.
|
accepts an input value followed by optional parameters and returns the transformed value.
|
||||||
|
|
||||||
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
* There will be one additional argument to the `transform` method for each parameter passed to the pipe.
|
||||||
Our pipe has one such parameter: the `exponent`.
|
Your pipe has one such parameter: the `exponent`.
|
||||||
|
* To tell Angular that this is a pipe, you apply the
|
||||||
* We tell Angular that this is a pipe by applying the
|
`@Pipe` #{_decorator}, which you import from the core Angular library.
|
||||||
`@Pipe` #{_decorator} which we import from the core Angular library.
|
* The `@Pipe` #{_decorator} allows you to define the
|
||||||
|
pipe name that you'll use within template expressions. It must be a valid JavaScript identifier.
|
||||||
* The `@Pipe` #{_decorator} allows us to define the
|
Your pipe's name is `exponentialStrength`.
|
||||||
pipe name that we'll use within template expressions. It must be a valid JavaScript identifier.
|
|
||||||
Our pipe's name is `exponentialStrength`.
|
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
### The *PipeTransform* Interface
|
### The *PipeTransform* interface
|
||||||
|
|
||||||
The `transform` method is essential to a pipe.
|
The `transform` method is essential to a pipe.
|
||||||
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
||||||
It is technically optional; Angular looks for and executes the `transform` method regardless.
|
Technically, it's optional; Angular looks for and executes the `transform` method regardless.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Now we need a component to demonstrate our pipe.
|
Now you need a component to demonstrate the pipe.
|
||||||
+makeExample('pipes/ts/src/app/power-booster.component.ts',null,'src/app/power-booster.component.ts')(format='.')
|
+makeExample('pipes/ts/src/app/power-booster.component.ts',null,'src/app/power-booster.component.ts')(format='.')
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||||||
|
@ -165,28 +164,28 @@ figure.image-display
|
||||||
- var _decls = _docsFor == 'dart' ? 'pipes' : 'declarations';
|
- var _decls = _docsFor == 'dart' ? 'pipes' : 'declarations';
|
||||||
- var _appMod = _docsFor == 'dart' ? '@Component' : 'AppModule';
|
- var _appMod = _docsFor == 'dart' ? '@Component' : 'AppModule';
|
||||||
:marked
|
:marked
|
||||||
Two things to note:
|
Note the following:
|
||||||
|
|
||||||
1. We use our custom pipe the same way we use built-in pipes.
|
* You use your custom pipe the same way you use built-in pipes.
|
||||||
1. We must include our pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
* You must include your pipe in the `!{_decls}` #{_array} of the `!{_appMod}`.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Remember the !{_decls} #{_array}!
|
header Remember the !{_decls} #{_array}
|
||||||
:marked
|
:marked
|
||||||
Angular reports an error if we neglect to list our custom pipe.
|
You must manually register custom pipes.
|
||||||
We didn't list the `DatePipe` in our previous example because all
|
If you don't, Angular reports an error.
|
||||||
|
In the previous example, you didn't list the `DatePipe` because all
|
||||||
Angular built-in pipes are pre-registered.
|
Angular built-in pipes are pre-registered.
|
||||||
Custom pipes must be registered manually.
|
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
If we try the <live-example></live-example>,
|
To probe the behavior in the <live-example></live-example>,
|
||||||
we can probe its behavior by changing the value and the optional exponent in the template.
|
change the value and optional exponent in the template.
|
||||||
|
|
||||||
## Power Boost Calculator (extra-credit)
|
## Power Boost Calculator
|
||||||
|
|
||||||
It's not much fun updating the template to test our custom pipe.
|
It's not much fun updating the template to test the custom pipe.
|
||||||
We could upgrade the example to a "Power Boost Calculator" that combines
|
Upgrade the example to a "Power Boost Calculator" that combines
|
||||||
our pipe and two-way data binding with `ngModel`.
|
your pipe and two-way data binding with `ngModel`.
|
||||||
|
|
||||||
+makeExample('src/app/power-boost-calculator.component.ts')
|
+makeExample('src/app/power-boost-calculator.component.ts')
|
||||||
|
|
||||||
|
@ -196,140 +195,140 @@ figure.image-display
|
||||||
.l-main-section
|
.l-main-section
|
||||||
a#change-detection
|
a#change-detection
|
||||||
:marked
|
:marked
|
||||||
## Pipes and Change Detection
|
## Pipes and change detection
|
||||||
|
|
||||||
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event:
|
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event:
|
||||||
every keystroke, mouse move, timer tick, and server response. This could be expensive.
|
every keystroke, mouse move, timer tick, and server response. This could be expensive.
|
||||||
Angular strives to lower the cost whenever possible and appropriate.
|
Angular strives to lower the cost whenever possible and appropriate.
|
||||||
|
|
||||||
Angular picks a simpler, faster change detection algorithm when we use a pipe. Let's see how.
|
Angular picks a simpler, faster change detection algorithm when you use a pipe.
|
||||||
|
|
||||||
### No pipe
|
### No pipe
|
||||||
|
|
||||||
The component in our next example uses the default, aggressive change detection strategy to monitor and update
|
In the next example, the component uses the default, aggressive change detection strategy to monitor and update
|
||||||
its display of every hero in the `heroes` #{_array}. Here's the template:
|
its display of every hero in the `heroes` #{_array}. Here's the template:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-1', 'src/app/flying-heroes.component.html (v1)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The companion component class provides heroes, adds new heroes into the #{_array}, and can reset the #{_array}.
|
The companion component class provides heroes, adds heroes into the #{_array}, and can reset the #{_array}.
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'v1', 'src/app/flying-heroes.component.ts (v1)')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We can add a new hero and Angular updates the display when we do.
|
You can add heroes and Angular updates the display when you do.
|
||||||
The `reset` button replaces `heroes` with a new #{_array} of the original heroes and Angular updates the display when we do.
|
If you click the `reset` button, Angular replaces `heroes` with a new #{_array} of the original heroes and updates the display.
|
||||||
If we added the ability to remove or change a hero, Angular would detect those changes too and update the display as well.
|
If you added the ability to remove or change a hero, Angular would detect those changes and update the display as well.
|
||||||
|
|
||||||
### Flying Heroes pipe
|
### Flying-heroes pipe
|
||||||
|
|
||||||
Let's add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
Add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-flying-heroes', 'src/app/flying-heroes.component.html (flyers)')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.html', 'template-flying-heroes', 'src/app/flying-heroes.component.html (flyers)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Here's the `FlyingHeroesPipe` implementation which follows the pattern for custom pipes we saw earlier.
|
Here's the `FlyingHeroesPipe` implementation, which follows the pattern for custom pipes described earlier.
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.pipe.ts', 'pure', 'src/app/flying-heroes.pipe.ts')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.pipe.ts', 'pure', 'src/app/flying-heroes.pipe.ts')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When we run the sample now we see odd behavior (try it in the <live-example></live-example>).
|
Notice the odd behavior in the <live-example></live-example>:
|
||||||
Every hero we add is a flying hero but none of them are displayed.
|
when you add flying heroes, none of them are displayed under "Heroes who fly."
|
||||||
|
|
||||||
Although we're not getting the behavior we want, Angular isn't broken.
|
Although you're not getting the behavior you want, Angular isn't broken.
|
||||||
It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.
|
It's just using a different change-detection algorithm that ignores changes to the list or any of its items.
|
||||||
|
|
||||||
Look at how we're adding a new hero:
|
Notice how a hero is added:
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.component.ts', 'push')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We're adding the new hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
You add the hero into the `heroes` #{_array}. The reference to the #{_array} hasn't changed.
|
||||||
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
It's the same #{_array}. That's all Angular cares about. From its perspective, *same #{_array}, no change, no display update*.
|
||||||
|
|
||||||
We can fix that. Let's create a new #{_array} with the new hero appended and assign that to `heroes`.
|
To fix that, create an #{_array} with the new hero appended and assign that to `heroes`.
|
||||||
This time Angular detects that the #{_array} reference has changed.
|
This time Angular detects that the #{_array} reference has changed.
|
||||||
It executes the pipe and updates the display with the new #{_array} which includes the new flying hero.
|
It executes the pipe and updates the display with the new #{_array}, which includes the new flying hero.
|
||||||
|
|
||||||
*If we **mutate** the #{_array}, no pipe is invoked and no display updated;
|
If you *mutate* the #{_array}, no pipe is invoked and the display isn't updated;
|
||||||
if we **replace** the #{_array}, then the pipe executes and the display is updated*.
|
if you *replace* the #{_array}, the pipe executes and the display is updated.
|
||||||
The *Flying Heroes* extends the
|
The Flying Heroes application extends the
|
||||||
code with checkbox switches and additional displays to help us experience these effects.
|
code with checkbox switches and additional displays to help you experience these effects.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Replacing the #{_array} is an efficient way to signal to Angular that it should update the display.
|
Replacing the #{_array} is an efficient way to signal Angular to update the display.
|
||||||
When do we replace the #{_array}? When the data change.
|
When do you replace the #{_array}? When the data change.
|
||||||
That's an easy rule to follow in *this toy* example
|
That's an easy rule to follow in *this* example
|
||||||
where the only way to change the data is by adding a new hero.
|
where the only way to change the data is by adding a hero.
|
||||||
|
|
||||||
More often we don't know when the data have changed,
|
More often, you don't know when the data have changed,
|
||||||
especially in applications that mutate data in many ways,
|
especially in applications that mutate data in many ways,
|
||||||
perhaps in application locations far away.
|
perhaps in application locations far away.
|
||||||
A component in such an application usually can't know about those changes.
|
A component in such an application usually can't know about those changes.
|
||||||
Moreover, it's unwise to distort our component design to accommodate a pipe.
|
Moreover, it's unwise to distort the component design to accommodate a pipe.
|
||||||
We strive as much as possible to keep the component class independent of the HTML.
|
Strive to keep the component class independent of the HTML.
|
||||||
The component should be unaware of pipes.
|
The component should be unaware of pipes.
|
||||||
|
|
||||||
Perhaps we should consider a different kind of pipe for filtering flying heroes, an *impure pipe*.
|
For filtering flying heroes, consider an *impure pipe*.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Pure and Impure Pipes
|
## Pure and impure pipes
|
||||||
|
|
||||||
There are two categories of pipes: **pure** and **impure**.
|
There are two categories of pipes: *pure* and *impure*.
|
||||||
Pipes are pure by default. Every pipe we've seen so far has been pure.
|
Pipes are pure by default. Every pipe you've seen so far has been pure.
|
||||||
We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe`
|
You make a pipe impure by setting its pure flag to false. You could make the `FlyingHeroesPipe`
|
||||||
impure like this:
|
impure like this:
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
|
+makeExample('pipes/ts/src/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe.
|
Before doing that, understand the difference between pure and impure, starting with a pure pipe.
|
||||||
|
|
||||||
### Pure pipes
|
### Pure pipes
|
||||||
|
|
||||||
block pure-change
|
block pure-change
|
||||||
:marked
|
:marked
|
||||||
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
|
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
|
||||||
A ***pure change*** is *either* a change to a primitive input value (`String`, `Number`, `Boolean`, `Symbol`)
|
A pure change is either a change to a primitive input value (`String`, `Number`, `Boolean`, `Symbol`)
|
||||||
*or* a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
or a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Angular ignores changes *within* (composite) objects.
|
Angular ignores changes within (composite) objects.
|
||||||
It won't call a pure pipe if we change an input month, add to an input #{_array}, or update an input object property.
|
It won't call a pure pipe if you change an input month, add to an input #{_array}, or update an input object property.
|
||||||
|
|
||||||
This may seem restrictive but it is also fast.
|
This may seem restrictive but it's also fast.
|
||||||
An object reference check is fast—much faster than a deep check for
|
An object reference check is fast—much faster than a deep check for
|
||||||
differences—so Angular can quickly determine if it can skip both the
|
differences—so Angular can quickly determine if it can skip both the
|
||||||
pipe execution and a view update.
|
pipe execution and a view update.
|
||||||
|
|
||||||
For this reason, we prefer a pure pipe if we can live with the change detection strategy.
|
For this reason, a pure pipe is preferable when you can live with the change detection strategy.
|
||||||
When we can't, we *may* turn to the impure pipe.
|
When you can't, you *can* use the impure pipe.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Or we might not use a pipe at all.
|
Or you might not use a pipe at all.
|
||||||
It may be better to pursue the pipe's purpose with a property of the component,
|
It may be better to pursue the pipe's purpose with a property of the component,
|
||||||
a point we take up later.
|
a point that's discussed later in this page.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Impure pipes
|
### Impure pipes
|
||||||
|
|
||||||
Angular executes an *impure pipe* during *every* component change detection cycle.
|
Angular executes an *impure pipe* during every component change detection cycle.
|
||||||
An impure pipe will be called a lot, as often as every keystroke or mouse-move.
|
An impure pipe is called often, as often as every keystroke or mouse-move.
|
||||||
|
|
||||||
With that concern in mind, we must implement an impure pipe with great care.
|
With that concern in mind, implement an impure pipe with great care.
|
||||||
An expensive, long-running pipe could destroy the user experience.
|
An expensive, long-running pipe could destroy the user experience.
|
||||||
|
|
||||||
<a id="impure-flying-heroes"></a>
|
<a id="impure-flying-heroes"></a>
|
||||||
### An impure *FlyingHeroesPipe*
|
### An impure *FlyingHeroesPipe*
|
||||||
|
|
||||||
A flip of the switch turns our `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
A flip of the switch turns the `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
||||||
Here's the complete implementation:
|
The complete implementation is as follows:
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
'pipes/ts/src/app/flying-heroes.pipe.ts, pipes/ts/src/app/flying-heroes.pipe.ts',
|
'pipes/ts/src/app/flying-heroes.pipe.ts, pipes/ts/src/app/flying-heroes.pipe.ts',
|
||||||
'impure, pure',
|
'impure, pure',
|
||||||
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
|
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
|
You inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
|
||||||
The only difference is the `pure` flag in the pipe metadata.
|
The only difference is the `pure` flag in the pipe metadata.
|
||||||
|
|
||||||
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
||||||
|
@ -337,7 +336,7 @@ block pure-change
|
||||||
+makeExcerpt('src/app/flying-heroes.pipe.ts','filter', '')
|
+makeExcerpt('src/app/flying-heroes.pipe.ts','filter', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
You can derive a `FlyingHeroesImpureComponent` from `FlyingHeroesComponent`.
|
||||||
|
|
||||||
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html';
|
- var _fnSuffix = _docsFor == 'dart' ? '.component.ts' : '-impure.component.html';
|
||||||
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
|
- var _region = _docsFor == 'dart' ? 'impure-component' : 'template-flying-heroes';
|
||||||
|
@ -345,21 +344,21 @@ block pure-change
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The only substantive change is the pipe in the template.
|
The only substantive change is the pipe in the template.
|
||||||
We can confirm in the <live-example></live-example> that the _flying heroes_
|
You can confirm in the <live-example></live-example> that the _flying heroes_
|
||||||
display updates as we enter new heroes even when we mutate the `heroes` #{_array}.
|
display updates as you add heroes, even when you mutate the `heroes` #{_array}.
|
||||||
|
|
||||||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
||||||
h3#async-pipe The impure #[i AsyncPipe]
|
h3#async-pipe The impure #[i AsyncPipe]
|
||||||
:marked
|
:marked
|
||||||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||||||
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
||||||
and subscribes to the input automatically, eventually returning the emitted value(s).
|
and subscribes to the input automatically, eventually returning the emitted values.
|
||||||
|
|
||||||
It is also stateful.
|
The `AsyncPipe` is also stateful.
|
||||||
The pipe maintains a subscription to the input `#{_Observable}` and
|
The pipe maintains a subscription to the input `#{_Observable}` and
|
||||||
keeps delivering values from that `#{_Observable}` as they arrive.
|
keeps delivering values from that `#{_Observable}` as they arrive.
|
||||||
|
|
||||||
In this next example, we bind an `#{_Observable}` of message strings
|
This next example binds an `#{_Observable}` of message strings
|
||||||
(`message#{_dollar}`) to a view with the `async` pipe.
|
(`message#{_dollar}`) to a view with the `async` pipe.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
|
+makeExample('pipes/ts/src/app/hero-async-message.component.ts', null, 'src/app/hero-async-message.component.ts')
|
||||||
|
@ -367,42 +366,41 @@ h3#async-pipe The impure #[i AsyncPipe]
|
||||||
:marked
|
:marked
|
||||||
The Async pipe saves boilerplate in the component code.
|
The Async pipe saves boilerplate in the component code.
|
||||||
The component doesn't have to subscribe to the async data source,
|
The component doesn't have to subscribe to the async data source,
|
||||||
it doesn't extract the resolved values and expose them for binding,
|
extract the resolved values and expose them for binding,
|
||||||
and the component doesn't have to unsubscribe when it is destroyed
|
and have to unsubscribe when it's destroyed
|
||||||
(a potent source of memory leaks).
|
(a potent source of memory leaks).
|
||||||
|
|
||||||
### An impure caching pipe
|
### An impure caching pipe
|
||||||
|
|
||||||
Let's write one more impure pipe, a pipe that makes an HTTP request.
|
Write one more impure pipe, a pipe that makes an HTTP request.
|
||||||
|
|
||||||
Remember that impure pipes are called every few milliseconds.
|
Remember that impure pipes are called every few milliseconds.
|
||||||
If we're not careful, this pipe will punish the server with requests.
|
If you're not careful, this pipe will punish the server with requests.
|
||||||
|
|
||||||
We are careful.
|
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
|
||||||
The pipe only calls the server when the request URL changes and it caches the server response.
|
The code<span if-docs="ts"> uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
||||||
Here's the code<span if-docs="ts">, which uses the [Angular http](server-communication.html) client to retrieve data</span>:
|
|
||||||
|
|
||||||
+makeExample('src/app/fetch-json.pipe.ts')
|
+makeExample('src/app/fetch-json.pipe.ts')
|
||||||
:marked
|
:marked
|
||||||
Then we demonstrate it in a harness component whose template defines two bindings to this pipe,
|
Now demonstrate it in a harness component whose template defines two bindings to this pipe,
|
||||||
both requesting the heroes from the `heroes.json` file.
|
both requesting the heroes from the `heroes.json` file.
|
||||||
|
|
||||||
+makeExample('src/app/hero-list.component.ts')
|
+makeExample('src/app/hero-list.component.ts')
|
||||||
:marked
|
:marked
|
||||||
The component renders like this:
|
The component renders as the following:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
A breakpoint on the pipe's request for data shows that
|
A breakpoint on the pipe's request for data shows the following:
|
||||||
* each binding gets its own pipe instance
|
* Each binding gets its own pipe instance.
|
||||||
* each pipe instance caches its own url and data
|
* Each pipe instance caches its own URL and data.
|
||||||
* each pipe instance only calls the server once
|
* Each pipe instance only calls the server once.
|
||||||
|
|
||||||
### *JsonPipe*
|
### *JsonPipe*
|
||||||
|
|
||||||
The second `fetch` pipe binding above demonstrates more pipe chaining.
|
In the previous code sample, the second `fetch` pipe binding demonstrates more pipe chaining.
|
||||||
It displays the same hero data in JSON format by chaining through to the built-in `JsonPipe`.
|
It displays the same hero data in JSON format by chaining through to the built-in `JsonPipe`.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
|
@ -417,25 +415,25 @@ a#pure-pipe-pure-fn
|
||||||
### Pure pipes and pure functions
|
### Pure pipes and pure functions
|
||||||
|
|
||||||
A pure pipe uses pure functions.
|
A pure pipe uses pure functions.
|
||||||
Pure functions process inputs and return values without detectable side-effects.
|
Pure functions process inputs and return values without detectable side effects.
|
||||||
Given the same input they should always return the same output.
|
Given the same input, they should always return the same output.
|
||||||
|
|
||||||
The pipes we saw earlier in this chapter were implemented with pure functions.
|
The pipes discussed earlier in this page are implemented with pure functions.
|
||||||
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
||||||
So is our `ExponentialStrengthPipe`.
|
So are the `ExponentialStrengthPipe` and `FlyingHeroesPipe`.
|
||||||
So is our `FlyingHeroesPipe`.
|
A few steps back, you reviewed the `FlyingHeroesImpurePipe`—an impure pipe with a pure function.
|
||||||
A few steps back we reviewed the `FlyingHeroesImpurePipe` — *an impure pipe with a pure function*.
|
|
||||||
|
|
||||||
But a *pure pipe* must always be implemented with a *pure function*. Failure to heed this warning will bring about many a console errors regarding expressions that have changed after they were checked.
|
But always implement a *pure pipe* with a *pure function*.
|
||||||
|
Otherwise, you'll see many console errors regarding expressions that changed after they were checked.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Next Steps
|
## Next steps
|
||||||
|
|
||||||
Pipes are a great way to encapsulate and share common display-value
|
Pipes are a great way to encapsulate and share common display-value
|
||||||
transformations. We use them like styles, dropping them
|
transformations. Use them like styles, dropping them
|
||||||
into our templates expressions to enrich the appeal and usability
|
into your template's expressions to enrich the appeal and usability
|
||||||
of our views.
|
of your views.
|
||||||
|
|
||||||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/#!?query=pipe).
|
Explore Angular's inventory of built-in pipes in the [API Reference](../api/#!?query=pipe).
|
||||||
Try writing a custom pipe and perhaps contributing it to the community.
|
Try writing a custom pipe and perhaps contributing it to the community.
|
||||||
|
@ -443,45 +441,45 @@ a#pure-pipe-pure-fn
|
||||||
a(id="no-filter-pipe")
|
a(id="no-filter-pipe")
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## No *FilterPipe* or *OrderByPipe*
|
## Appendix: No *FilterPipe* or *OrderByPipe*
|
||||||
|
|
||||||
Angular does not ship with pipes for filtering or sorting lists.
|
Angular doesn't provide pipes for filtering or sorting lists.
|
||||||
Developers familiar with AngularJS know these as `filter` and `orderBy`.
|
Developers familiar with AngularJS know these as `filter` and `orderBy`.
|
||||||
There are no equivalents in Angular.
|
There are no equivalents in Angular.
|
||||||
|
|
||||||
This is not an oversight. Angular is unlikely to offer such pipes because
|
This isn't an oversight. Angular doesn't offer such pipes because
|
||||||
(a) they perform poorly and (b) they prevent aggressive minification.
|
they perform poorly and prevent aggressive minification.
|
||||||
Both `filter` and `orderBy` require parameters that reference object properties.
|
Both `filter` and `orderBy` require parameters that reference object properties.
|
||||||
We learned earlier that such pipes must be [*impure*](#pure-and-impure-pipes) and that
|
Earlier in this page, you learned that such pipes must be [impure](#pure-and-impure-pipes) and that
|
||||||
Angular calls impure pipes in almost every change detection cycle.
|
Angular calls impure pipes in almost every change-detection cycle.
|
||||||
|
|
||||||
Filtering and especially sorting are expensive operations.
|
Filtering and especially sorting are expensive operations.
|
||||||
The user experience can degrade severely for even moderate sized lists when Angular calls these pipe methods many times per second.
|
The user experience can degrade severely for even moderate-sized lists when Angular calls these pipe methods many times per second.
|
||||||
The `filter` and `orderBy` have often been abused in AngularJS apps, leading to complaints that Angular itself is slow.
|
`filter` and `orderBy` have often been abused in AngularJS apps, leading to complaints that Angular itself is slow.
|
||||||
That charge is fair in the indirect sense that AngularJS prepared this performance trap
|
That charge is fair in the indirect sense that AngularJS prepared this performance trap
|
||||||
by offering `filter` and `orderBy` in the first place.
|
by offering `filter` and `orderBy` in the first place.
|
||||||
|
|
||||||
The minification hazard is also compelling if less obvious. Imagine a sorting pipe applied to a list of heroes.
|
The minification hazard is also compelling, if less obvious. Imagine a sorting pipe applied to a list of heroes.
|
||||||
We might sort the list by hero `name` and `planet` of origin properties something like this:
|
The list might be sorted by hero `name` and `planet` of origin properties in the following way:
|
||||||
code-example(language="html")
|
code-example(language="html")
|
||||||
<!-- NOT REAL CODE! -->
|
<!-- NOT REAL CODE! -->
|
||||||
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
|
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>
|
||||||
:marked
|
:marked
|
||||||
We identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
You identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
||||||
(e.g., `hero['name']`).
|
(such as `hero['name']`).
|
||||||
Unfortunately, aggressive minification *munges* the `Hero` property names so that `Hero.name` and `Hero.planet`
|
Unfortunately, aggressive minification manipulates the `Hero` property names so that `Hero.name` and `Hero.planet`
|
||||||
becomes something like `Hero.a` and `Hero.b`. Clearly `hero['name']` is not going to work.
|
become something like `Hero.a` and `Hero.b`. Clearly `hero['name']` doesn't work.
|
||||||
|
|
||||||
Some of us may not care to minify this aggressively. That's *our* choice.
|
While some may not care to minify this aggressively,
|
||||||
But the Angular product should not prevent someone else from minifying aggressively.
|
the Angular product shouldn't prevent anyone from minifying aggressively.
|
||||||
Therefore, the Angular team decided that everything shipped in Angular will minify safely.
|
Therefore, the Angular team decided that everything Angular provides will minify safely.
|
||||||
|
|
||||||
The Angular team and many experienced Angular developers strongly recommend that you move
|
The Angular team and many experienced Angular developers strongly recommend moving
|
||||||
filtering and sorting logic into the component itself.
|
filtering and sorting logic into the component itself.
|
||||||
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
|
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
|
||||||
over when and how often to execute the supporting logic.
|
over when and how often to execute the supporting logic.
|
||||||
Any capabilities that you would have put in a pipe and shared across the app can be
|
Any capabilities that you would have put in a pipe and shared across the app can be
|
||||||
written in a filtering/sorting service and injected into the component.
|
written in a filtering/sorting service and injected into the component.
|
||||||
|
|
||||||
If these performance and minification considerations do not apply to you, you can always create your own such pipes
|
If these performance and minification considerations don't apply to you, you can always create your own such pipes
|
||||||
(along the lines of the [FlyingHeroesPipe](#impure-flying-heroes)) or find them in the community.
|
(similar to the [FlyingHeroesPipe](#impure-flying-heroes)) or find them in the community.
|
||||||
|
|
Loading…
Reference in New Issue