docs: rewrite property binding section and add example (#25770)
PR Close #25770
This commit is contained in:
parent
4ad323a4d6
commit
85d38ae564
|
@ -0,0 +1,54 @@
|
||||||
|
import { browser, element, by } from 'protractor';
|
||||||
|
|
||||||
|
|
||||||
|
describe('Property binding e2e tests', () => {
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
browser.get('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Property Binding with Angular', function () {
|
||||||
|
expect(element(by.css('h1')).getText()).toEqual('Property Binding with Angular');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display four phone pictures', function() {
|
||||||
|
expect(element.all(by.css('img')).isPresent()).toBe(true);
|
||||||
|
expect(element.all(by.css('img')).count()).toBe(4);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Disabled button', function () {
|
||||||
|
expect(element.all(by.css('button')).get(0).getText()).toBe(`Disabled Button`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Binding to a property of a directive', function () {
|
||||||
|
expect(element.all(by.css('h2')).get(4).getText()).toBe(`Binding to a property of a directive`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Your item is: lamp', function () {
|
||||||
|
expect(element.all(by.css('p')).get(0).getText()).toContain(`blue`);
|
||||||
|
});
|
||||||
|
it('should display Your item is: lamp', function () {
|
||||||
|
expect(element.all(by.css('p')).get(1).getText()).toContain(`Your item is: lamp`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Your item is: parentItem', function () {
|
||||||
|
expect(element.all(by.css('p')).get(2).getText()).toBe(`Your item is: parentItem`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a ul', function () {
|
||||||
|
expect(element.all(by.css('ul')).get(0).getText()).toContain(`tv`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a ul containing phone', function () {
|
||||||
|
expect(element.all(by.css('ul')).get(1).getText()).toBe(`21 phone`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display one-time initialized string', function () {
|
||||||
|
expect(element.all(by.css('p')).get(3).getText()).toContain(`one-time initialized`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display Malicious content', function () {
|
||||||
|
expect(element.all(by.css('h2')).get(8).getText()).toBe(`Malicious content`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
div {
|
||||||
|
margin: 1rem auto;
|
||||||
|
width: 90%
|
||||||
|
}
|
||||||
|
.special {
|
||||||
|
background-color: #1976d2;
|
||||||
|
color: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>Property Binding with Angular</h1>
|
||||||
|
<h2>Binding the src property of an image:</h2>
|
||||||
|
<!-- #docregion property-binding -->
|
||||||
|
<img [src]="itemImageUrl">
|
||||||
|
<!-- #enddocregion property-binding -->
|
||||||
|
<h2>Using bind- syntax:</h2>
|
||||||
|
<!-- #docregion bind-prefix -->
|
||||||
|
<img bind-src="itemImageUrl">
|
||||||
|
<!-- #enddocregion bind-prefix -->
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Binding to the colSpan property</h2>
|
||||||
|
<table border=1>
|
||||||
|
<tr><td>Column 1</td><td>Column 2</td></tr>
|
||||||
|
<!-- #docregion colSpan -->
|
||||||
|
<!-- Notice the colSpan property is camel case -->
|
||||||
|
<tr><td [colSpan]="2">Span 2 columns</td></tr>
|
||||||
|
<!-- #enddocregion colSpan -->
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Button disabled state bound to isUnchanged property:</h2>
|
||||||
|
<!-- #docregion disabled-button -->
|
||||||
|
<!-- Bind button disabled state to `isUnchanged` property -->
|
||||||
|
<button [disabled]="isUnchanged">Disabled Button</button>
|
||||||
|
<!-- #enddocregion disabled-button -->
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Binding to a property of a directive</h2>
|
||||||
|
<!-- #docregion class-binding -->
|
||||||
|
<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>
|
||||||
|
<!-- #enddocregion class-binding -->
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Model property of a custom component:</h2>
|
||||||
|
<!-- #docregion model-property-binding -->
|
||||||
|
<app-item-detail [childItem]="parentItem"></app-item-detail>
|
||||||
|
<!-- #enddocregion model-property-binding -->
|
||||||
|
<!-- #docregion no-evaluation -->
|
||||||
|
<app-item-detail childItem="parentItem"></app-item-detail>
|
||||||
|
<!-- #enddocregion no-evaluation -->
|
||||||
|
|
||||||
|
<h3>Pass objects:</h3>
|
||||||
|
<!-- #docregion pass-object -->
|
||||||
|
<app-list-item [items]="currentItem"></app-list-item>
|
||||||
|
<!-- #enddocregion pass-object -->
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Initialized string:</h2>
|
||||||
|
<!-- #docregion string-init -->
|
||||||
|
<app-string-init prefix="This is a one-time initialized string."></app-string-init>
|
||||||
|
<!-- #enddocregion string-init -->
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Property binding and interpolation</h2>
|
||||||
|
<!-- #docregion property-binding-interpolation -->
|
||||||
|
<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
|
||||||
|
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
|
||||||
|
|
||||||
|
<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
|
||||||
|
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>
|
||||||
|
<!-- #enddocregion property-binding-interpolation -->
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Malicious content</h2>
|
||||||
|
|
||||||
|
<!-- #docregion malicious-interpolated -->
|
||||||
|
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
|
||||||
|
<!-- #enddocregion malicious-interpolated -->
|
||||||
|
|
||||||
|
<!-- #docregion malicious-content -->
|
||||||
|
<!--
|
||||||
|
Angular generates a warning for the following line as it sanitizes them
|
||||||
|
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
|
||||||
|
-->
|
||||||
|
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||||
|
<!-- #enddocregion malicious-content -->
|
||||||
|
</div>
|
|
@ -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!');
|
||||||
|
}));
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.css']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
itemImageUrl = '../assets/phone.png';
|
||||||
|
isUnchanged = true;
|
||||||
|
classes = 'special';
|
||||||
|
// #docregion parent-data-type
|
||||||
|
parentItem = 'lamp';
|
||||||
|
// #enddocregion parent-data-type
|
||||||
|
|
||||||
|
// #docregion pass-object
|
||||||
|
currentItem = [{
|
||||||
|
id: 21,
|
||||||
|
name: 'phone'
|
||||||
|
}];
|
||||||
|
// #enddocregion pass-object
|
||||||
|
|
||||||
|
interpolationTitle = 'Interpolation';
|
||||||
|
propertyTitle = 'Property binding';
|
||||||
|
|
||||||
|
// #docregion malicious-content
|
||||||
|
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';
|
||||||
|
// #enddocregion malicious-content
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { ItemDetailComponent } from './item-detail/item-detail.component';
|
||||||
|
import { ListItemComponent } from './list-item/list-item.component';
|
||||||
|
import { StringInitComponent } from './string-init/string-init.component';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
ItemDetailComponent,
|
||||||
|
ListItemComponent,
|
||||||
|
StringInitComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
|
@ -0,0 +1,4 @@
|
||||||
|
<p>Your item is: {{ childItem }} </p>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ItemDetailComponent } from './item-detail.component';
|
||||||
|
|
||||||
|
describe('ItemDetailComponent', () => {
|
||||||
|
let component: ItemDetailComponent;
|
||||||
|
let fixture: ComponentFixture<ItemDetailComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ItemDetailComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemDetailComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
// import { Item } from '../item';
|
||||||
|
// import { ITEMS } from '../mock-items';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-item-detail',
|
||||||
|
templateUrl: './item-detail.component.html',
|
||||||
|
styleUrls: ['./item-detail.component.css']
|
||||||
|
})
|
||||||
|
export class ItemDetailComponent implements OnInit {
|
||||||
|
|
||||||
|
// #docregion input-type
|
||||||
|
@Input() childItem: string;
|
||||||
|
// #enddocregion input-type
|
||||||
|
|
||||||
|
// items = ITEMS;
|
||||||
|
|
||||||
|
|
||||||
|
currentItem = 'bananas in boxes';
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// #docregion item-class
|
||||||
|
export class Item {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
// #enddocregion item-class
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
<h4>Nested component's list of items:</h4>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let item of listItems">{{item.id}} {{item.name}}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>Pass an object from parent to nested component:</h4>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let item of items">{{item.id}} {{item.name}}</li>
|
||||||
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ListItemComponent } from './list-item.component';
|
||||||
|
|
||||||
|
describe('ItemListComponent', () => {
|
||||||
|
let component: ListItemComponent;
|
||||||
|
let fixture: ComponentFixture<ListItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ListItemComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ListItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { ITEMS } from '../mock-items';
|
||||||
|
import { Item } from '../item';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-list-item',
|
||||||
|
templateUrl: './list-item.component.html',
|
||||||
|
styleUrls: ['./list-item.component.css']
|
||||||
|
})
|
||||||
|
export class ListItemComponent {
|
||||||
|
listItems = ITEMS;
|
||||||
|
// #docregion item-input
|
||||||
|
@Input() items: Item[];
|
||||||
|
// #enddocregion item-input
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Item } from './item';
|
||||||
|
|
||||||
|
export const ITEMS: Item[] = [
|
||||||
|
{ id: 11, name: 'bottle' },
|
||||||
|
{ id: 12, name: 'boombox' },
|
||||||
|
{ id: 13, name: 'chair' },
|
||||||
|
{ id: 14, name: 'fishbowl' },
|
||||||
|
{ id: 15, name: 'lamp' },
|
||||||
|
{ id: 16, name: 'tv' },
|
||||||
|
{ id: 17, name: 'mug' },
|
||||||
|
{ id: 18, name: 'paintbrush' },
|
||||||
|
{ id: 19, name: 'plant' },
|
||||||
|
{ id: 20, name: 'teapot' }
|
||||||
|
];
|
|
@ -0,0 +1 @@
|
||||||
|
<p>{{prefix}}</p>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StringInitComponent } from './string-init.component';
|
||||||
|
|
||||||
|
describe('StringInitComponent', () => {
|
||||||
|
let component: StringInitComponent;
|
||||||
|
let fixture: ComponentFixture<StringInitComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ StringInitComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(StringInitComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-string-init',
|
||||||
|
templateUrl: './string-init.component.html',
|
||||||
|
styleUrls: ['./string-init.component.css']
|
||||||
|
})
|
||||||
|
export class StringInitComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() prefix: string;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>PropertyBinding</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>
|
|
@ -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));
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"description": "Property Binding",
|
||||||
|
"files": [
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js",
|
||||||
|
"!**/*.[0,1,2].*"
|
||||||
|
],
|
||||||
|
"file": "src/app/app.component.ts",
|
||||||
|
"tags": ["property binding"]
|
||||||
|
}
|
|
@ -643,188 +643,252 @@ The following table summarizes:
|
||||||
|
|
||||||
{@a property-binding}
|
{@a property-binding}
|
||||||
|
|
||||||
## Property binding ( <span class="syntax">[property]</span> )
|
## Property binding `[property]`
|
||||||
|
|
||||||
Write a template **property binding** to set a property of a view element.
|
Use property binding to _set_ properties of target elements or
|
||||||
The binding sets the property to the value of a [template expression](guide/template-syntax#template-expressions).
|
directive `@Input()` decorators. For an example
|
||||||
|
demonstrating all of the points in this section, see the
|
||||||
|
<live-example name="property-binding">property binding example</live-example>.
|
||||||
|
|
||||||
The most common property binding sets an element property to a component property value. An example is
|
### One-way in
|
||||||
binding the `src` property of an image element to a component's `heroImageUrl` property:
|
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-1" header="src/app/app.component.html" linenums="false">
|
Property binding flows a value in one direction,
|
||||||
|
from a component's property into a target element property.
|
||||||
|
|
||||||
|
You can't use property
|
||||||
|
binding to read or pull values out of target elements. Similarly, you cannot use
|
||||||
|
property binding to call a method on the target element.
|
||||||
|
If the element raises events, you can listen to them with an [event binding](guide/template-syntax#event-binding).
|
||||||
|
|
||||||
|
If you must read a target element property or call one of its methods,
|
||||||
|
see the API reference for [ViewChild](api/core/ViewChild) and
|
||||||
|
[ContentChild](api/core/ContentChild).
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
The most common property binding sets an element property to a component
|
||||||
|
property value. An example is
|
||||||
|
binding the `src` property of an image element to a component's `itemImageUrl` property:
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
Here's an example of binding to the `colSpan` property. Notice that it's not `colspan`,
|
||||||
|
which is the attribute, spelled with a lowercase `s`.
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/app.component.html" region="colSpan" header="src/app/app.component.html" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
For more details, see the [MDN HTMLTableCellElment](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) documentation.
|
||||||
|
|
||||||
|
<!-- Add link when Attribute Binding updates are merged:
|
||||||
|
For more about `colSpan` and `colspan`, see (Attribute Binding)[guide/template-syntax]. -->
|
||||||
|
|
||||||
Another example is disabling a button when the component says that it `isUnchanged`:
|
Another example is disabling a button when the component says that it `isUnchanged`:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-2" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="disabled-button" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Another is setting a property of a directive:
|
Another is setting a property of a directive:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-3" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="class-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Yet another is setting the model property of a custom component (a great way
|
Yet another is setting the model property of a custom component—a great way
|
||||||
for parent and child components to communicate):
|
for parent and child components to communicate:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-4" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
### One-way *in*
|
|
||||||
|
|
||||||
People often describe property binding as *one-way data binding* because it flows a value in one direction,
|
|
||||||
from a component's data property into a target element property.
|
|
||||||
|
|
||||||
You cannot use property binding to pull values *out* of the target element.
|
|
||||||
You can't bind to a property of the target element to _read_ it. You can only _set_ it.
|
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
|
||||||
|
|
||||||
Similarly, you cannot use property binding to *call* a method on the target element.
|
|
||||||
|
|
||||||
If the element raises events, you can listen to them with an [event binding](guide/template-syntax#event-binding).
|
|
||||||
|
|
||||||
If you must read a target element property or call one of its methods,
|
|
||||||
you'll need a different technique.
|
|
||||||
See the API reference for
|
|
||||||
[ViewChild](api/core/ViewChild) and
|
|
||||||
[ContentChild](api/core/ContentChild).
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Binding target
|
### Binding target
|
||||||
|
|
||||||
An element property between enclosing square brackets identifies the target property.
|
An element property between enclosing square brackets identifies
|
||||||
|
the target property.
|
||||||
The target property in the following code is the image element's `src` property.
|
The target property in the following code is the image element's `src` property.
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-1" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Some people prefer the `bind-` prefix alternative, known as the *canonical form*:
|
There's also the `bind-` prefix alternative:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-5" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="bind-prefix" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The target name is always the name of a property, even when it appears to be the name of something else.
|
|
||||||
You see `src` and may think it's the name of an attribute. No. It's the name of an image element property.
|
In most cases, the target name is the name of a property, even
|
||||||
|
when it appears to be the name of an attribute.
|
||||||
|
So in this case, `src` is the name of the `<img>` element property.
|
||||||
|
|
||||||
Element properties may be the more common targets,
|
Element properties may be the more common targets,
|
||||||
but Angular looks first to see if the name is a property of a known directive,
|
but Angular looks first to see if the name is a property of a known directive,
|
||||||
as it is in the following example:
|
as it is in the following example:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-3" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="class-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
Technically, Angular is matching the name to a directive `@Input()`,
|
||||||
|
one of the property names listed in the directive's `inputs` array
|
||||||
Technically, Angular is matching the name to a directive [input](guide/template-syntax#inputs-outputs),
|
or a property decorated with `@Input()`.
|
||||||
one of the property names listed in the directive's `inputs` array or a property decorated with `@Input()`.
|
|
||||||
Such inputs map to the directive's own properties.
|
Such inputs map to the directive's own properties.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error.
|
If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error.
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Though the target name is usually the name of a property,
|
||||||
|
there is an automatic attribute-to-property mapping in Angular for
|
||||||
|
several common attributes. These include `class`/`className`, `innerHtml`/`innerHTML`, and
|
||||||
|
`tabindex`/`tabIndex`.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### Avoid side effects
|
### Avoid side effects
|
||||||
|
|
||||||
As mentioned previously, evaluation of a template expression should have no visible side effects.
|
Evaluation of a template expression should have no visible side effects.
|
||||||
The expression language itself does its part to keep you safe.
|
The expression language itself, or the way you write template expressions,
|
||||||
You can't assign a value to anything in a property binding expression nor use the increment and decrement operators.
|
helps to a certain extent;
|
||||||
|
you can't assign a value to anything in a property binding expression
|
||||||
|
nor use the increment and decrement operators.
|
||||||
|
|
||||||
Of course, the expression might invoke a property or method that has side effects.
|
For example, you could have an expression that invoked a property or method that had
|
||||||
Angular has no way of knowing that or stopping you.
|
side effects. The expression could call something like `getFoo()` where only you
|
||||||
|
know what `getFoo()` does. If `getFoo()` changes something
|
||||||
The expression could call something like `getFoo()`. Only you know what `getFoo()` does.
|
and you happen to be binding to that something,
|
||||||
If `getFoo()` changes something and you happen to be binding to that something, you risk an unpleasant experience.
|
Angular may or may not display the changed value. Angular may detect the
|
||||||
Angular may or may not display the changed value. Angular may detect the change and throw a warning error.
|
change and throw a warning error.
|
||||||
In general, stick to data properties and to methods that return values and do no more.
|
As a best practice, stick to properties and to methods that return
|
||||||
|
values and avoid side effects.
|
||||||
|
|
||||||
### Return the proper type
|
### Return the proper type
|
||||||
|
|
||||||
The template expression should evaluate to the type of value expected by the target property.
|
The template expression should evaluate to the type of value
|
||||||
Return a string if the target property expects a string.
|
that the target property expects.
|
||||||
Return a number if the target property expects a number.
|
Return a string if the target property expects a string, a number if it
|
||||||
Return an object if the target property expects an object.
|
expects a number, an object if it expects an object, and so on.
|
||||||
|
|
||||||
The `hero` property of the `HeroDetail` component expects a `Hero` object, which is exactly what you're sending in the property binding:
|
In the following example, the `childItem` property of the `ItemDetailComponent` expects a string, which is exactly what you're sending in the property binding:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-4" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
You can confirm this by looking in the `ItemDetailComponent` where the `@Input` type is set to a string:
|
||||||
|
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts (setting the @Input() type" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
As you can see here, the `parentItem` in `AppComponent` is a string, which the `ItemDetailComponent` expects:
|
||||||
|
<code-example path="property-binding/src/app/app.component.ts" region="parent-data-type" header="src/app/app.component.ts" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
#### Passing in an object
|
||||||
|
|
||||||
|
The previous simple example showed passing in a string. To pass in an object,
|
||||||
|
the syntax and thinking are the same.
|
||||||
|
|
||||||
|
In this scenario, `ListItemComponent` is nested within `AppComponent` and the `item` property expects an object.
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/app.component.html" region="pass-object" header="src/app/app.component.html" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The `item` property is declared in the `ListItemComponent` with a type of `Item` and decorated with `@Input()`:
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/list-item/list-item.component.ts" region="item-input" header="src/app/list-item.component.ts" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
In this sample app, an `Item` is an object that has two properties; an `id` and a `name`.
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/item.ts" region="item-class" header="src/app/item.ts" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
While a list of items exists in another file, `mock-items.ts`, you can
|
||||||
|
specify a different item in `app.component.ts` so that the new item will render:
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/app.component.ts" region="pass-object" header="src/app.component.ts" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
You just have to make sure, in this case, that you're supplying an object because that's the type of `item` and is what the nested component, `ListItemComponent`, expects.
|
||||||
|
|
||||||
|
In this example, `AppComponent` specifies a different `item` object
|
||||||
|
(`currentItem`) and passes it to the nested `ListItemComponent`. `ListItemComponent` was able to use `currentItem` because it matches what an `Item` object is according to `item.ts`. The `item.ts` file is where
|
||||||
|
`ListItemComponent` gets its definition of an `item`.
|
||||||
|
|
||||||
### Remember the brackets
|
### Remember the brackets
|
||||||
|
|
||||||
The brackets tell Angular to evaluate the template expression.
|
The brackets, `[]`, tell Angular to evaluate the template expression.
|
||||||
If you omit the brackets, Angular treats the string as a constant
|
If you omit the brackets, Angular treats the string as a constant
|
||||||
and *initializes the target property* with that string.
|
and *initializes the target property* with that string:
|
||||||
It does *not* evaluate the string!
|
|
||||||
|
|
||||||
Don't make the following mistake:
|
<code-example path="property-binding/src/app/app.component.html" region="no-evaluation" header="src/app.component.html" linenums="false">
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-6" header="src/app/app.component.html" linenums="false">
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
{@a one-time-initialization}
|
|
||||||
|
Omitting the brackets will render the string
|
||||||
|
`parentItem`, not the value of `parentItem`.
|
||||||
|
|
||||||
### One-time string initialization
|
### One-time string initialization
|
||||||
|
|
||||||
You *should* omit the brackets when all of the following are true:
|
You *should* omit the brackets when all of the following are true:
|
||||||
|
|
||||||
* The target property accepts a string value.
|
* The target property accepts a string value.
|
||||||
* The string is a fixed value that you can bake into the template.
|
* The string is a fixed value that you can put directly into the template.
|
||||||
* This initial value never changes.
|
* This initial value never changes.
|
||||||
|
|
||||||
You routinely initialize attributes this way in standard HTML, and it works
|
You routinely initialize attributes this way in standard HTML, and it works
|
||||||
just as well for directive and component property initialization.
|
just as well for directive and component property initialization.
|
||||||
The following example initializes the `prefix` property of the `HeroDetailComponent` to a fixed string,
|
The following example initializes the `prefix` property of the `StringInitComponent` to a fixed string,
|
||||||
not a template expression. Angular sets it and forgets about it.
|
not a template expression. Angular sets it and forgets about it.
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-7" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="string-init" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The `[hero]` binding, on the other hand, remains a live binding to the component's `currentHero` property.
|
The `[item]` binding, on the other hand, remains a live binding to the component's `currentItem` property.
|
||||||
|
|
||||||
{@a property-binding-or-interpolation}
|
### Property binding vs. interpolation
|
||||||
|
|
||||||
### Property binding or interpolation?
|
|
||||||
|
|
||||||
You often have a choice between interpolation and property binding.
|
You often have a choice between interpolation and property binding.
|
||||||
The following binding pairs do the same thing:
|
The following binding pairs do the same thing:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-vs-interpolation" header="src/app/app.component.html" linenums="false">
|
<code-example path="property-binding/src/app/app.component.html" region="property-binding-interpolation" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
_Interpolation_ is a convenient alternative to _property binding_ in many cases.
|
Interpolation is a convenient alternative to property binding in
|
||||||
|
many cases. When rendering data values as strings, there is no
|
||||||
|
technical reason to prefer one form to the other, though readability
|
||||||
|
tends to favor interpolation. However, *when setting an element
|
||||||
|
property to a non-string data value, you must use property binding*.
|
||||||
|
|
||||||
When rendering data values as strings, there is no technical reason to prefer one form to the other.
|
### Content security
|
||||||
You lean toward readability, which tends to favor interpolation.
|
|
||||||
You suggest establishing coding style rules and choosing the form that
|
|
||||||
both conforms to the rules and feels most natural for the task at hand.
|
|
||||||
|
|
||||||
When setting an element property to a non-string data value, you must use _property binding_.
|
Imagine the following malicious content.
|
||||||
|
|
||||||
#### Content security
|
<code-example path="property-binding/src/app/app.component.ts" region="malicious-content" header="src/app/app.component.ts" linenums="false">
|
||||||
|
|
||||||
Imagine the following *malicious content*.
|
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.ts" region="evil-title" header="src/app/app.component.ts" linenums="false">
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Fortunately, Angular data binding is on alert for dangerous HTML.
|
In the component template, the content might be used with interpolation:
|
||||||
It [*sanitizes*](guide/security#sanitization-and-security-contexts) the values before displaying them.
|
|
||||||
It **will not** allow HTML with script tags to leak into the browser, neither with interpolation
|
<code-example path="property-binding/src/app/app.component.html" region="malicious-interpolated" header="src/app/app.component.ts" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Fortunately, Angular data binding is on alert for dangerous HTML. In the above case,
|
||||||
|
the HTML displays as is, and the Javascript does not execute. Angular **does not**
|
||||||
|
allow HTML with script tags to leak into the browser, neither with interpolation
|
||||||
nor property binding.
|
nor property binding.
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-vs-interpolation-sanitization" header="src/app/app.component.html" linenums="false">
|
In the following example, however, Angular [sanitizes](guide/security#sanitization-and-security-contexts)
|
||||||
|
the values before displaying them.
|
||||||
|
|
||||||
|
<code-example path="property-binding/src/app/app.component.html" region="malicious-content" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Interpolation handles the script tags differently than property binding but both approaches render the
|
Interpolation handles the `<script>` tags differently than
|
||||||
content harmlessly.
|
property binding but both approaches render the
|
||||||
|
content harmlessly. The following is the browser output
|
||||||
|
of the `evilTitle` examples.
|
||||||
<figure>
|
|
||||||
<img src='generated/images/guide/template-syntax/evil-title.png' alt="evil title made safe">
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
|
```
|
||||||
|
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
||||||
|
"Template alert("evil never sleeps")Syntax" is the property bound evil title.
|
||||||
|
```
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
{@a other-bindings}
|
{@a other-bindings}
|
||||||
|
@ -855,7 +919,7 @@ table span attributes. They are pure attributes.
|
||||||
They do not correspond to element properties, and they do not set element properties.
|
They do not correspond to element properties, and they do not set element properties.
|
||||||
There are no property targets to bind to.
|
There are no property targets to bind to.
|
||||||
|
|
||||||
This fact becomes painfully obvious when you write something like this.
|
This fact becomes obvious when you write something like this.
|
||||||
|
|
||||||
<code-example language="html">
|
<code-example language="html">
|
||||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||||
|
|
Loading…
Reference in New Issue