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}
|
||||
|
||||
## Property binding ( <span class="syntax">[property]</span> )
|
||||
## Property binding `[property]`
|
||||
|
||||
Write a template **property binding** to set a property of a view element.
|
||||
The binding sets the property to the value of a [template expression](guide/template-syntax#template-expressions).
|
||||
Use property binding to _set_ properties of target elements or
|
||||
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
|
||||
binding the `src` property of an image element to a component's `heroImageUrl` property:
|
||||
### One-way in
|
||||
|
||||
<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>
|
||||
|
||||
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`:
|
||||
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
Yet another is setting the model property of a custom component (a great way
|
||||
for parent and child components to communicate):
|
||||
Yet another is setting the model property of a custom component—a great way
|
||||
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>
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
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,
|
||||
but Angular looks first to see if the name is a property of a known directive,
|
||||
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>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Technically, Angular is matching the name to a directive [input](guide/template-syntax#inputs-outputs),
|
||||
one of the property names listed in the directive's `inputs` array or a property decorated with `@Input()`.
|
||||
Technically, Angular is matching the name to a directive `@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.
|
||||
|
||||
</div>
|
||||
|
||||
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
|
||||
|
||||
As mentioned previously, evaluation of a template expression should have no visible side effects.
|
||||
The expression language itself does its part to keep you safe.
|
||||
You can't assign a value to anything in a property binding expression nor use the increment and decrement operators.
|
||||
Evaluation of a template expression should have no visible side effects.
|
||||
The expression language itself, or the way you write template expressions,
|
||||
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.
|
||||
Angular has no way of knowing that or stopping you.
|
||||
|
||||
The expression could call something like `getFoo()`. Only you know what `getFoo()` does.
|
||||
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 change and throw a warning error.
|
||||
In general, stick to data properties and to methods that return values and do no more.
|
||||
For example, you could have an expression that invoked a property or method that had
|
||||
side effects. The expression could call something like `getFoo()` where only you
|
||||
know what `getFoo()` does. If `getFoo()` changes something
|
||||
and you happen to be binding to that something,
|
||||
Angular may or may not display the changed value. Angular may detect the
|
||||
change and throw a warning error.
|
||||
As a best practice, stick to properties and to methods that return
|
||||
values and avoid side effects.
|
||||
|
||||
### Return the proper type
|
||||
|
||||
The template expression should evaluate to the type of value expected by the target property.
|
||||
Return a string if the target property expects a string.
|
||||
Return a number if the target property expects a number.
|
||||
Return an object if the target property expects an object.
|
||||
The template expression should evaluate to the type of value
|
||||
that the target property expects.
|
||||
Return a string if the target property expects a string, a number if it
|
||||
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>
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
and *initializes the target property* with that string.
|
||||
It does *not* evaluate the string!
|
||||
and *initializes the target property* with that string:
|
||||
|
||||
Don't make the following mistake:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-6" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="property-binding/src/app/app.component.html" region="no-evaluation" header="src/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a one-time-initialization}
|
||||
|
||||
Omitting the brackets will render the string
|
||||
`parentItem`, not the value of `parentItem`.
|
||||
|
||||
### One-time string initialization
|
||||
|
||||
You *should* omit the brackets when all of the following are true:
|
||||
|
||||
* 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.
|
||||
|
||||
You routinely initialize attributes this way in standard HTML, and it works
|
||||
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.
|
||||
|
||||
<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>
|
||||
|
||||
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 or interpolation?
|
||||
### Property binding vs. interpolation
|
||||
|
||||
You often have a choice between interpolation and property binding.
|
||||
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>
|
||||
|
||||
_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.
|
||||
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.
|
||||
### Content security
|
||||
|
||||
When setting an element property to a non-string data value, you must use _property binding_.
|
||||
Imagine the following malicious content.
|
||||
|
||||
#### Content security
|
||||
|
||||
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 path="property-binding/src/app/app.component.ts" region="malicious-content" header="src/app/app.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Fortunately, Angular data binding is on alert for dangerous HTML.
|
||||
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
|
||||
In the component template, the content might be used 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.
|
||||
|
||||
<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>
|
||||
|
||||
Interpolation handles the script tags differently than property binding but both approaches render the
|
||||
content harmlessly.
|
||||
|
||||
|
||||
<figure>
|
||||
<img src='generated/images/guide/template-syntax/evil-title.png' alt="evil title made safe">
|
||||
</figure>
|
||||
Interpolation handles the `<script>` tags differently than
|
||||
property binding but both approaches render the
|
||||
content harmlessly. The following is the browser output
|
||||
of the `evilTitle` examples.
|
||||
|
||||
```
|
||||
"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/>
|
||||
{@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.
|
||||
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">
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
|
@ -910,7 +974,7 @@ 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:
|
||||
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>
|
||||
|
|
Loading…
Reference in New Issue