docs: rewrite attribute binding section and add example (#26004)

PR Close #26004
This commit is contained in:
Kapunahele Wong 2018-09-10 16:30:24 -04:00 committed by Andrew Kushnir
parent e76690be29
commit b5aa0473fc
11 changed files with 288 additions and 67 deletions

View File

@ -0,0 +1,47 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor';
describe('Attribute binding example', function () {
beforeEach(function () {
browser.get('');
});
it('should display Property Binding with Angular', function () {
expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
});
it('should display a table', function() {
expect(element.all(by.css('table')).isPresent()).toBe(true);
});
it('should display an Aria button', function () {
expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
});
it('should display a blue background on div', function () {
expect(element.all(by.css('div')).get(1).getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)');
});
it('should display a blue div with a red border', function () {
expect(element.all(by.css('div')).get(4).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
});
it('should display a div with replaced classes', function () {
expect(element.all(by.css('div')).get(5).getAttribute('class')).toEqual('new-class');
});
it('should display four buttons', function() {
let redButton = element.all(by.css('button')).get(1);
let saveButton = element.all(by.css('button')).get(2);
let bigButton = element.all(by.css('button')).get(3);
let smallButton = element.all(by.css('button')).get(4);
expect(redButton.getCssValue('color')).toEqual('rgba(255, 0, 0, 1)');
expect(saveButton.getCssValue('background-color')).toEqual('rgba(0, 255, 255, 1)');
expect(bigButton.getText()).toBe('Big');
expect(smallButton.getText()).toBe('Small');
});
});

View File

@ -0,0 +1,22 @@
.special {
background-color: #1976d2;
color: #ffffff;
}
.item {
font-weight: bold;
}
.clearance {
border: 2px solid #d41e2e;
}
.item-clearance {
font-style: italic;
}
.new-class {
background-color: #ed1b2f;
font-style: italic;
color: #fff;
}

View File

@ -0,0 +1,65 @@
<h1>Attribute, class, and style bindings</h1>
<h2>Attribute binding</h2>
<!-- #docregion attrib-binding-colspan -->
<table border=1>
<!-- expression calculates colspan=2 -->
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
<!-- ERROR: There is no `colspan` property to set!
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
-->
<!-- #docregion colSpan -->
<!-- Notice the colSpan property is camel case -->
<tr><td [colSpan]="1 + 1">Three-Four</td></tr>
<!-- #enddocregion colSpan -->
<tr><td>Five</td><td>Six</td></tr>
</table>
<!-- #enddocregion attrib-binding-colspan -->
<div>
<!-- #docregion attrib-binding-aria -->
<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
<!-- #enddocregion attrib-binding-aria -->
</div>
<hr />
<h2>Class binding</h2>
<!-- #docregion is-special -->
<h3>toggle the "special" class on/off with a property:</h3>
<div [class.special]="isSpecial">The class binding is special.</div>
<h3>binding to class.special overrides the class attribute:</h3>
<div class="special" [class.special]="!isSpecial">This one is not so special.</div>
<h3>Using the bind- syntax:</h3>
<div bind-class.special="isSpecial">This class binding is special too.</div>
<!-- #enddocregion is-special -->
<!-- #docregion add-class -->
<h3>Add a class:</h3>
<div class="item clearance special" [class.item-clearance]="itemClearance">Add another class</div>
<!-- #enddocregion add-class -->
<!-- #docregion class-override -->
<h3>Overwrite all existing classes with a new class:</h3>
<div class="item clearance special" [attr.class]="resetClasses">Reset all classes at once</div>
<!-- #enddocregion class-override -->
<hr />
<h2>Style binding</h2>
<!-- #docregion style-binding-->
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
<!-- #enddocregion style-binding -->
<!-- #docregion style-binding-condition-->
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
<!-- #enddocregion style-binding-condition-->

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
actionName = 'Go for it';
isSpecial = true;
itemClearance = true;
resetClasses = 'new-class';
canSave = true;
}

View File

@ -0,0 +1,18 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AttributeBinding</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Attribute Binding",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Attribute Binding"]
}

View File

@ -895,108 +895,103 @@ of the `evilTitle` examples.
## Attribute, class, and style bindings
The template syntax provides specialized one-way bindings for scenarios less well suited to property binding.
The template syntax provides specialized one-way bindings for scenarios less well-suited to property binding.
To see attribute, class, and style bindings in a functioning app, see the <live-example name="attribute-binding"></live-example> especially for this section.
### Attribute binding
You can set the value of an attribute directly with an **attribute binding**.
Set the value of an attribute directly with an **attribute binding**. This is the only exception to the rule that a binding sets a target property and the only binding that creates and sets an attribute.
Usually, setting an element property with a [property binding](guide/template-syntax#property-binding)
is preferable to setting the attribute with a string. However, sometimes
there is no element property to bind, so attribute binding is the solution.
Consider the [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) and
[SVG](https://developer.mozilla.org/en-US/docs/Web/SVG). They are purely attributes, don't correspond to element properties, and don't set element properties. In these cases, there are no property targets to bind to.
Attribute binding syntax resembles property binding, but
instead of an element property between brackets, start with the prefix `attr`,
followed by a dot (`.`), and the name of the attribute.
You then set the attribute value, using an expression that resolves to a string,
or remove the attribute when the expression resolves to `null`.
One of the primary use cases for attribute binding
is to set ARIA attributes, as in this example:
<code-example path="attribute-binding/src/app/app.component.html" region="attrib-binding-aria" header="src/app/app.component.html" linenums="false">
</code-example>
<div class="alert is-helpful">
This is the only exception to the rule that a binding sets a target property.
This is the only binding that creates and sets an attribute.
#### `colspan` and `colSpan`
</div>
Notice the difference between the `colspan` attribute and the `colSpan` property.
This guide stresses repeatedly that setting an element property with a property binding
is always preferred to setting the attribute with a string. Why does Angular offer attribute binding?
**You must use attribute binding when there is no element property to bind.**
Consider the [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA),
[SVG](https://developer.mozilla.org/en-US/docs/Web/SVG), and
table span attributes. They are pure attributes.
They do not correspond to element properties, and they do not set element properties.
There are no property targets to bind to.
This fact becomes obvious when you write something like this.
If you wrote something like this:
<code-example language="html">
&lt;tr&gt;&lt;td colspan="{{1 + 1}}"&gt;Three-Four&lt;/td&gt;&lt;/tr&gt;
</code-example>
And you get this error:
You'd get this error:
<code-example format="nocode">
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
</code-example>
As the message says, the `<td>` element does not have a `colspan` property.
It has the "colspan" *attribute*, but
interpolation and property binding can set only *properties*, not attributes.
As the message says, the `<td>` element does not have a `colspan` property. This is true
because `colspan` is an attribute&mdash;`colSpan`, with a capital `S`, is the
corresponding property. Interpolation and property binding can set only *properties*, not attributes.
You need attribute bindings to create and bind to such attributes.
Instead, you'd use property binding and write it like this:
Attribute binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix **`attr`**,
followed by a dot (`.`) and the name of the attribute.
You then set the attribute value, using an expression that resolves to a string.
Bind `[attr.colspan]` to a calculated value:
<code-example path="template-syntax/src/app/app.component.html" region="attrib-binding-colspan" header="src/app/app.component.html" linenums="false">
<code-example path="attribute-binding/src/app/app.component.html" region="colSpan" header="src/app/app.component.html" linenums="false">
</code-example>
Here's how the table renders:
<table border="1px">
<tr><td colspan="2">One-Two</td></tr>
<tr><td>Five</td><td>Six</td></tr>
</table>
One of the primary use cases for attribute binding
is to set ARIA attributes, as in this example:
<code-example path="template-syntax/src/app/app.component.html" region="attrib-binding-aria" header="src/app/app.component.html" linenums="false">
</code-example>
</div>
<hr/>
### Class binding
You can add and remove CSS class names from an element's `class` attribute with
Add and remove CSS class names from an element's `class` attribute with
a **class binding**.
Class binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix `class`,
Here's how to set the attribute without binding in plain HTML:
```html
<!-- standard class attribute setting -->
<div class="item clearance special">Item clearance special</div>
```
Class binding syntax resembles property binding, but instead of an element property between brackets, start with the prefix `class`,
optionally followed by a dot (`.`) and the name of a CSS class: `[class.class-name]`.
The following examples show how to add and remove the application's "special" class
with class bindings. Here's how to set the attribute without binding:
<code-example path="template-syntax/src/app/app.component.html" region="class-binding-1" header="src/app/app.component.html" linenums="false">
</code-example>
You can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding.
<code-example path="template-syntax/src/app/app.component.html" region="class-binding-2" header="src/app/app.component.html" linenums="false">
<code-example path="attribute-binding/src/app/app.component.html" region="class-override" header="src/app/app.component.html" linenums="false">
</code-example>
You can also add append a class to an element without overwriting the classes already on the element:
<code-example path="attribute-binding/src/app/app.component.html" region="add-class" header="src/app/app.component.html" linenums="false">
</code-example>
Finally, you can bind to a specific class name.
Angular adds the class when the template expression evaluates to truthy.
It removes the class when the expression is falsy.
<code-example path="template-syntax/src/app/app.component.html" region="class-binding-3" header="src/app/app.component.html" linenums="false">
<code-example path="attribute-binding/src/app/app.component.html" region="is-special" header="src/app/app.component.html" linenums="false">
</code-example>
<div class="alert is-helpful">
While this is a fine way to toggle a single class name,
the [NgClass directive](guide/template-syntax#ngClass) is usually preferred when managing multiple class names at the same time.
</div>
While this technique is suitable for toggling a single class name,
consider the [`NgClass`](guide/template-syntax#ngClass) directive when
managing multiple class names at the same time.
<hr/>
@ -1009,21 +1004,17 @@ Style binding syntax resembles property binding.
Instead of an element property between brackets, start with the prefix `style`,
followed by a dot (`.`) and the name of a CSS style property: `[style.style-property]`.
<code-example path="template-syntax/src/app/app.component.html" region="style-binding-1" header="src/app/app.component.html" linenums="false">
<code-example path="attribute-binding/src/app/app.component.html" region="style-binding" header="src/app/app.component.html" linenums="false">
</code-example>
Some style binding styles have a unit extension.
The following example conditionally sets the font size in “em” and “%” units .
<code-example path="template-syntax/src/app/app.component.html" region="style-binding-2" header="src/app/app.component.html" linenums="false">
<code-example path="attribute-binding/src/app/app.component.html" region="style-binding-condition" header="src/app/app.component.html" linenums="false">
</code-example>
<div class="alert is-helpful">
While this is a fine way to set a single style,
the [NgStyle directive](guide/template-syntax#ngStyle) is generally preferred when setting several inline styles at the same time.
</div>
**This technique is suitable for setting a single style, but consider
the [`NgStyle`](guide/template-syntax#ngStyle) directive when setting several inline styles at the same time.**
<div class="alert is-helpful">