From b5aa0473fc4a2a30e264f15287dee9bb095912a9 Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Mon, 10 Sep 2018 16:30:24 -0400 Subject: [PATCH] docs: rewrite attribute binding section and add example (#26004) PR Close #26004 --- .../attribute-binding/e2e/src/app.e2e-spec.ts | 47 +++++++ .../attribute-binding/example-config.json | 0 .../src/app/app.component.css | 22 +++ .../src/app/app.component.html | 65 +++++++++ .../src/app/app.component.spec.ts | 27 ++++ .../src/app/app.component.ts | 15 +++ .../attribute-binding/src/app/app.module.ts | 18 +++ .../examples/attribute-binding/src/index.html | 14 ++ .../examples/attribute-binding/src/main.ts | 12 ++ .../attribute-binding/stackblitz.json | 10 ++ aio/content/guide/template-syntax.md | 125 ++++++++---------- 11 files changed, 288 insertions(+), 67 deletions(-) create mode 100644 aio/content/examples/attribute-binding/e2e/src/app.e2e-spec.ts create mode 100644 aio/content/examples/attribute-binding/example-config.json create mode 100644 aio/content/examples/attribute-binding/src/app/app.component.css create mode 100644 aio/content/examples/attribute-binding/src/app/app.component.html create mode 100644 aio/content/examples/attribute-binding/src/app/app.component.spec.ts create mode 100644 aio/content/examples/attribute-binding/src/app/app.component.ts create mode 100644 aio/content/examples/attribute-binding/src/app/app.module.ts create mode 100644 aio/content/examples/attribute-binding/src/index.html create mode 100644 aio/content/examples/attribute-binding/src/main.ts create mode 100644 aio/content/examples/attribute-binding/stackblitz.json diff --git a/aio/content/examples/attribute-binding/e2e/src/app.e2e-spec.ts b/aio/content/examples/attribute-binding/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..c02fa87e1b --- /dev/null +++ b/aio/content/examples/attribute-binding/e2e/src/app.e2e-spec.ts @@ -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'); + }); + +}); diff --git a/aio/content/examples/attribute-binding/example-config.json b/aio/content/examples/attribute-binding/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/attribute-binding/src/app/app.component.css b/aio/content/examples/attribute-binding/src/app/app.component.css new file mode 100644 index 0000000000..f26e6c8569 --- /dev/null +++ b/aio/content/examples/attribute-binding/src/app/app.component.css @@ -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; +} diff --git a/aio/content/examples/attribute-binding/src/app/app.component.html b/aio/content/examples/attribute-binding/src/app/app.component.html new file mode 100644 index 0000000000..7a2fb3311f --- /dev/null +++ b/aio/content/examples/attribute-binding/src/app/app.component.html @@ -0,0 +1,65 @@ + +

Attribute, class, and style bindings

+

Attribute binding

+ + + + + + + + + + + + +
One-Two
Three-Four
FiveSix
+ + +
+ + + + +
+ +
+ +

Class binding

+ + +

toggle the "special" class on/off with a property:

+
The class binding is special.
+ +

binding to class.special overrides the class attribute:

+
This one is not so special.
+ +

Using the bind- syntax:

+
This class binding is special too.
+ + + +

Add a class:

+
Add another class
+ + + +

Overwrite all existing classes with a new class:

+
Reset all classes at once
+ + +
+ +

Style binding

+ + + + + + + + + + diff --git a/aio/content/examples/attribute-binding/src/app/app.component.spec.ts b/aio/content/examples/attribute-binding/src/app/app.component.spec.ts new file mode 100644 index 0000000000..bcbdf36b3e --- /dev/null +++ b/aio/content/examples/attribute-binding/src/app/app.component.spec.ts @@ -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!'); + })); +}); diff --git a/aio/content/examples/attribute-binding/src/app/app.component.ts b/aio/content/examples/attribute-binding/src/app/app.component.ts new file mode 100644 index 0000000000..f79b22c817 --- /dev/null +++ b/aio/content/examples/attribute-binding/src/app/app.component.ts @@ -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; + +} diff --git a/aio/content/examples/attribute-binding/src/app/app.module.ts b/aio/content/examples/attribute-binding/src/app/app.module.ts new file mode 100644 index 0000000000..926975afe8 --- /dev/null +++ b/aio/content/examples/attribute-binding/src/app/app.module.ts @@ -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 { } diff --git a/aio/content/examples/attribute-binding/src/index.html b/aio/content/examples/attribute-binding/src/index.html new file mode 100644 index 0000000000..0cfaba9b75 --- /dev/null +++ b/aio/content/examples/attribute-binding/src/index.html @@ -0,0 +1,14 @@ + + + + + AttributeBinding + + + + + + + + + diff --git a/aio/content/examples/attribute-binding/src/main.ts b/aio/content/examples/attribute-binding/src/main.ts new file mode 100644 index 0000000000..91ec6da5f0 --- /dev/null +++ b/aio/content/examples/attribute-binding/src/main.ts @@ -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)); diff --git a/aio/content/examples/attribute-binding/stackblitz.json b/aio/content/examples/attribute-binding/stackblitz.json new file mode 100644 index 0000000000..1a0178b68a --- /dev/null +++ b/aio/content/examples/attribute-binding/stackblitz.json @@ -0,0 +1,10 @@ +{ + "description": "Attribute Binding", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "file": "src/app/app.component.ts", + "tags": ["Attribute Binding"] +} diff --git a/aio/content/guide/template-syntax.md b/aio/content/guide/template-syntax.md index 8165cf751c..d42d9b1ce8 100644 --- a/aio/content/guide/template-syntax.md +++ b/aio/content/guide/template-syntax.md @@ -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 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: + + +
-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` -
+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: <tr><td colspan="{{1 + 1}}">Three-Four</td></tr> -And you get this error: +You'd get this error: Template parse errors: Can't bind to 'colspan' since it isn't a known native property -As the message says, the `` 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 `` element does not have a `colspan` property. This is true +because `colspan` is an attribute—`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: - - + -Here's how the table renders: - - - - -
One-Two
FiveSix
- -One of the primary use cases for attribute binding -is to set ARIA attributes, as in this example: - - - +
### 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 + +
Item clearance special
+``` + +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: - - - - You can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding. - + + + + +You can also add append a class to an element without overwriting the classes already on the element: + + 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. - + -
- -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. - -
+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.
@@ -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]`. - + Some style binding styles have a unit extension. The following example conditionally sets the font size in “em” and “%” units . - + -
- -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. - -
+**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.**