diff --git a/aio/content/examples/property-binding/e2e/src/app.e2e-spec.ts b/aio/content/examples/property-binding/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..07b98491c1 --- /dev/null +++ b/aio/content/examples/property-binding/e2e/src/app.e2e-spec.ts @@ -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`); + }); +}); diff --git a/aio/content/examples/property-binding/example-config.json b/aio/content/examples/property-binding/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/property-binding/src/app/app.component.css b/aio/content/examples/property-binding/src/app/app.component.css new file mode 100644 index 0000000000..91bae844fd --- /dev/null +++ b/aio/content/examples/property-binding/src/app/app.component.css @@ -0,0 +1,9 @@ +div { + margin: 1rem auto; + width: 90% +} +.special { + background-color: #1976d2; + color: #fff; + padding: 1rem; +} diff --git a/aio/content/examples/property-binding/src/app/app.component.html b/aio/content/examples/property-binding/src/app/app.component.html new file mode 100644 index 0000000000..e6ca6e5b0a --- /dev/null +++ b/aio/content/examples/property-binding/src/app/app.component.html @@ -0,0 +1,84 @@ + + +
+

Property Binding with Angular

+

Binding the src property of an image:

+ + + +

Using bind- syntax:

+ + + +
+ +

Binding to the colSpan property

+ + + + + + +
Column 1Column 2
Span 2 columns
+ + +
+

Button disabled state bound to isUnchanged property:

+ + + + +
+ +

Binding to a property of a directive

+ +

[ngClass] binding to the classes property making this blue

+ +
+ +

Model property of a custom component:

+ + + + + + + +

Pass objects:

+ + + + +
+

Initialized string:

+ + + + +
+ +

Property binding and interpolation

+ +

is the interpolated image.

+

is the property bound image.

+ +

"{{interpolationTitle}}" is the interpolated title.

+

"" is the property bound title.

+ + +
+ +

Malicious content

+ + +

"{{evilTitle}}" is the interpolated evil title.

+ + + + +

"" is the property bound evil title.

+ +
diff --git a/aio/content/examples/property-binding/src/app/app.component.spec.ts b/aio/content/examples/property-binding/src/app/app.component.spec.ts new file mode 100644 index 0000000000..bcbdf36b3e --- /dev/null +++ b/aio/content/examples/property-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/property-binding/src/app/app.component.ts b/aio/content/examples/property-binding/src/app/app.component.ts new file mode 100644 index 0000000000..c8dd3b5fe4 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/app.component.ts @@ -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 Syntax'; + // #enddocregion malicious-content +} diff --git a/aio/content/examples/property-binding/src/app/app.module.ts b/aio/content/examples/property-binding/src/app/app.module.ts new file mode 100644 index 0000000000..24c86b6bcf --- /dev/null +++ b/aio/content/examples/property-binding/src/app/app.module.ts @@ -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 { } diff --git a/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.css b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.html b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.html new file mode 100644 index 0000000000..050f915d47 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.html @@ -0,0 +1,4 @@ +

Your item is: {{ childItem }}

+ + + diff --git a/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.spec.ts b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.spec.ts new file mode 100644 index 0000000000..7559cb65f6 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ItemDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.ts b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.ts new file mode 100644 index 0000000000..e16be07036 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/item-detail/item-detail.component.ts @@ -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() { + } + +} diff --git a/aio/content/examples/property-binding/src/app/item.ts b/aio/content/examples/property-binding/src/app/item.ts new file mode 100644 index 0000000000..53bcca4514 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/item.ts @@ -0,0 +1,7 @@ +// #docregion item-class +export class Item { + id: number; + name: string; +} +// #enddocregion item-class + diff --git a/aio/content/examples/property-binding/src/app/list-item/list-item.component.css b/aio/content/examples/property-binding/src/app/list-item/list-item.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/property-binding/src/app/list-item/list-item.component.html b/aio/content/examples/property-binding/src/app/list-item/list-item.component.html new file mode 100644 index 0000000000..f19e6c605e --- /dev/null +++ b/aio/content/examples/property-binding/src/app/list-item/list-item.component.html @@ -0,0 +1,11 @@ + +

Nested component's list of items:

+ + +

Pass an object from parent to nested component:

+ + diff --git a/aio/content/examples/property-binding/src/app/list-item/list-item.component.spec.ts b/aio/content/examples/property-binding/src/app/list-item/list-item.component.spec.ts new file mode 100644 index 0000000000..0568b6c4c2 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/list-item/list-item.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ListItemComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ListItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/property-binding/src/app/list-item/list-item.component.ts b/aio/content/examples/property-binding/src/app/list-item/list-item.component.ts new file mode 100644 index 0000000000..48d1992cde --- /dev/null +++ b/aio/content/examples/property-binding/src/app/list-item/list-item.component.ts @@ -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() { } + +} diff --git a/aio/content/examples/property-binding/src/app/mock-items.ts b/aio/content/examples/property-binding/src/app/mock-items.ts new file mode 100644 index 0000000000..43cd74c5c9 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/mock-items.ts @@ -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' } +]; diff --git a/aio/content/examples/property-binding/src/app/string-init/string-init.component.css b/aio/content/examples/property-binding/src/app/string-init/string-init.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/property-binding/src/app/string-init/string-init.component.html b/aio/content/examples/property-binding/src/app/string-init/string-init.component.html new file mode 100644 index 0000000000..b3cd69e133 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/string-init/string-init.component.html @@ -0,0 +1 @@ +

{{prefix}}

diff --git a/aio/content/examples/property-binding/src/app/string-init/string-init.component.spec.ts b/aio/content/examples/property-binding/src/app/string-init/string-init.component.spec.ts new file mode 100644 index 0000000000..2c8e97ec82 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/string-init/string-init.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StringInitComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StringInitComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/property-binding/src/app/string-init/string-init.component.ts b/aio/content/examples/property-binding/src/app/string-init/string-init.component.ts new file mode 100644 index 0000000000..a49bf41093 --- /dev/null +++ b/aio/content/examples/property-binding/src/app/string-init/string-init.component.ts @@ -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() { + } + +} diff --git a/aio/content/examples/property-binding/src/assets/phone.png b/aio/content/examples/property-binding/src/assets/phone.png new file mode 100644 index 0000000000..6e60e1f1a1 Binary files /dev/null and b/aio/content/examples/property-binding/src/assets/phone.png differ diff --git a/aio/content/examples/property-binding/src/index.html b/aio/content/examples/property-binding/src/index.html new file mode 100644 index 0000000000..01fd8c14ff --- /dev/null +++ b/aio/content/examples/property-binding/src/index.html @@ -0,0 +1,14 @@ + + + + + PropertyBinding + + + + + + + + + diff --git a/aio/content/examples/property-binding/src/main.ts b/aio/content/examples/property-binding/src/main.ts new file mode 100644 index 0000000000..91ec6da5f0 --- /dev/null +++ b/aio/content/examples/property-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/property-binding/stackblitz.json b/aio/content/examples/property-binding/stackblitz.json new file mode 100644 index 0000000000..89869613e4 --- /dev/null +++ b/aio/content/examples/property-binding/stackblitz.json @@ -0,0 +1,10 @@ +{ + "description": "Property Binding", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0,1,2].*" + ], + "file": "src/app/app.component.ts", + "tags": ["property binding"] +} diff --git a/aio/content/guide/template-syntax.md b/aio/content/guide/template-syntax.md index c77b1f0765..f07416384e 100644 --- a/aio/content/guide/template-syntax.md +++ b/aio/content/guide/template-syntax.md @@ -643,188 +643,252 @@ The following table summarizes: {@a property-binding} -## Property binding ( [property] ) +## 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 +property binding 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 - +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: + + +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`. + + + + +For more details, see the [MDN HTMLTableCellElment](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) documentation. + + + Another example is disabling a button when the component says that it `isUnchanged`: - + Another is setting a property of a directive: - + -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: - + -### 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. - -
- -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). - -
- ### 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. - + -Some people prefer the `bind-` prefix alternative, known as the *canonical form*: +There's also the `bind-` prefix alternative: - + -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 `` 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: - + -
- -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. -
- If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error. +
+ +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`. + +
+ + ### 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: - + +You can confirm this by looking in the `ItemDetailComponent` where the `@Input` type is set to a string: + + + +As you can see here, the `parentItem` in `AppComponent` is a string, which the `ItemDetailComponent` expects: + + + +#### 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. + + + + +The `item` property is declared in the `ListItemComponent` with a type of `Item` and decorated with `@Input()`: + + + + +In this sample app, an `Item` is an object that has two properties; an `id` and a `name`. + + + + +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: + + + + +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: - - + -{@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. - + -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: - + -_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*. - - + -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: + + + + +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. - +In the following example, however, Angular [sanitizes](guide/security#sanitization-and-security-contexts) +the values before displaying them. + + -Interpolation handles the script tags differently than property binding but both approaches render the -content harmlessly. - - -
- evil title made safe -
+Interpolation handles the ` Syntax" is the interpolated evil title. +"Template alert("evil never sleeps")Syntax" is the property bound evil title. +```
{@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. <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: