diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.html b/public/docs/_examples/template-syntax/ts/app/app.component.html
index ed43a2ff33..57f73c4cb2 100644
--- a/public/docs/_examples/template-syntax/ts/app/app.component.html
+++ b/public/docs/_examples/template-syntax/ts/app/app.component.html
@@ -197,13 +197,18 @@ button
-Interpolated:
-Property bound:
+
is the interpolated image.
+ is the property bound image.
-The interpolated title is {{title}}
-
+"{{title}}" is the interpolated title.
+"" is the property bound title.
+
+"{{evilTitle}}" is the interpolated evil title.
+"" is the property bound evil title.
+
+
top
diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.ts b/public/docs/_examples/template-syntax/ts/app/app.component.ts
index 33ddaa9bf1..c4e32c74d1 100644
--- a/public/docs/_examples/template-syntax/ts/app/app.component.ts
+++ b/public/docs/_examples/template-syntax/ts/app/app.component.ts
@@ -1,4 +1,5 @@
-//#docplaster
+/* tslint:disable:member-ordering forin */
+// #docplaster
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
import { NgForm } from '@angular/common';
@@ -8,7 +9,7 @@ import { HeroDetailComponent, BigHeroDetailComponent } from './hero-detail.compo
import { MyClickDirective, MyClickDirective2 } from './my-click.directive';
// Alerter fn: monkey patch during test
-export function alerter(msg?:string) {
+export function alerter(msg?: string) {
window.alert(msg);
}
@@ -27,7 +28,7 @@ export enum Color {Red, Green, Blue};
})
export class AppComponent implements AfterViewInit, OnInit {
- ngOnInit(){
+ ngOnInit() {
this.refreshHeroes();
}
@@ -40,43 +41,48 @@ export class AppComponent implements AfterViewInit, OnInit {
badCurly = 'bad curly';
classes = 'special';
- callFax(value:string) {this.alert(`Faxing ${value} ...`)}
- callPhone(value:string) {this.alert(`Calling ${value} ...`)}
+ callFax(value: string) {this.alert(`Faxing ${value} ...`); }
+ callPhone(value: string) {this.alert(`Calling ${value} ...`); }
canSave = true;
Color = Color;
color = Color.Red;
- colorToggle() {this.color = (this.color === Color.Red)? Color.Blue : Color.Red}
+ colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; }
currentHero = Hero.MockHeroes[0];
- deleteHero(hero:Hero){
- this.alert('Deleted hero: '+ (hero && hero.firstName))
+ deleteHero(hero: Hero) {
+ this.alert('Deleted hero: ' + (hero && hero.firstName));
}
- // DevMode memoization fields
- private priorClasses:{};
- private _priorStyles:{};
- private _priorStyles2:{};
+ // #docregion evil-title
+ evilTitle = 'Template Syntax';
+ // #enddocregion evil-title
- getStyles(el:Element){
+ title = 'Template Syntax';
+
+ // DevMode memoization fields
+ private priorClasses: {};
+ private _priorStyles: {};
+
+ getStyles(el: Element) {
let styles = window.getComputedStyle(el);
let showStyles = {};
- for (var p in this.setStyles()){
+ for (let p in this.setStyles()) {
showStyles[p] = styles[p];
}
return JSON.stringify(showStyles);
}
- getVal() {return this.val};
+ getVal() { return this.val; }
- heroes:Hero[];
+ heroes: Hero[];
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png';
- //iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
+ // iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
clicked = '';
clickMessage = '';
clickMessage2 = '';
@@ -85,28 +91,28 @@ export class AppComponent implements AfterViewInit, OnInit {
isSpecial = true;
isUnchanged = true;
- nullHero:Hero = null; // or undefined
+ nullHero: Hero = null; // or undefined
- onCancel(event:KeyboardEvent){
- let evtMsg = event ? ' Event target is '+ (event.target).innerHTML : '';
- this.alert('Canceled.'+evtMsg)
+ onCancel(event: KeyboardEvent) {
+ let evtMsg = event ? ' Event target is ' + (event.target).innerHTML : '';
+ this.alert('Canceled.' + evtMsg);
}
- onClickMe(event:KeyboardEvent){
- let evtMsg = event ? ' Event target class is '+ (event.target).className : '';
- this.alert('Click me.'+evtMsg)
+ onClickMe(event: KeyboardEvent) {
+ let evtMsg = event ? ' Event target class is ' + (event.target).className : '';
+ this.alert('Click me.' + evtMsg);
}
- onSave(event:KeyboardEvent){
- let evtMsg = event ? ' Event target is '+ (event.target).innerText : '';
- this.alert('Saved.'+evtMsg)
+ onSave(event: KeyboardEvent) {
+ let evtMsg = event ? ' Event target is ' + (event.target).innerText : '';
+ this.alert('Saved.' + evtMsg);
}
- onSubmit(form:NgForm){
+ onSubmit(form: NgForm) {
let evtMsg = form.valid ?
- ' Form value is '+ JSON.stringify(form.value) :
+ ' Form value is ' + JSON.stringify(form.value) :
' Form is invalid';
- this.alert('Form submitted.'+evtMsg)
+ this.alert('Form submitted.' + evtMsg);
}
product = {
@@ -123,10 +129,10 @@ export class AppComponent implements AfterViewInit, OnInit {
// #docregion same-as-it-ever-was
private samenessCount = 5;
- moreOfTheSame() {this.samenessCount++;};
+ moreOfTheSame() { this.samenessCount++; };
get sameAsItEverWas() {
- var result:string[] = Array(this.samenessCount);
- for (var i=result.length; i-- > 0;){result[i]='same as it ever was ...'}
+ let result: string[] = Array(this.samenessCount);
+ for ( let i = result.length; i-- > 0; ) { result[i] = 'same as it ever was ...'; }
return result;
// return [1,2,3,4,5].map(id => {
// return {id:id, text: 'same as it ever was ...'};
@@ -134,8 +140,8 @@ export class AppComponent implements AfterViewInit, OnInit {
}
// #enddocregion same-as-it-ever-was
- setUpperCaseFirstName(firstName:string){
- //console.log(firstName);
+ setUpperCaseFirstName(firstName: string) {
+ // console.log(firstName);
this.currentHero.firstName = firstName.toUpperCase();
}
@@ -145,10 +151,10 @@ export class AppComponent implements AfterViewInit, OnInit {
saveable: this.canSave, // true
modified: !this.isUnchanged, // false
special: this.isSpecial, // true
- }
+ };
// #enddocregion setClasses
// compensate for DevMode (sigh)
- if (JSON.stringify(classes) === JSON.stringify(this.priorClasses)){
+ if (JSON.stringify(classes) === JSON.stringify(this.priorClasses)) {
return this.priorClasses;
}
this.priorClasses = classes;
@@ -165,10 +171,10 @@ export class AppComponent implements AfterViewInit, OnInit {
'font-style': this.canSave ? 'italic' : 'normal', // italic
'font-weight': !this.isUnchanged ? 'bold' : 'normal', // normal
'font-size': this.isSpecial ? '24px' : '8px', // 24px
- }
+ };
// #enddocregion setStyles
// compensate for DevMode (sigh)
- if (JSON.stringify(styles) === JSON.stringify(this._priorStyles)){
+ if (JSON.stringify(styles) === JSON.stringify(this._priorStyles)) {
return this._priorStyles;
}
this._priorStyles = styles;
@@ -178,15 +184,14 @@ export class AppComponent implements AfterViewInit, OnInit {
// #enddocregion setStyles
toeChoice = '';
- toeChooser(picker:HTMLFieldSetElement){
+ toeChooser(picker: HTMLFieldSetElement) {
let choices = picker.children;
- for (let i=0; ichoices[i];
- if (choice.checked) {return this.toeChoice = choice.value}
+ for (let i = 0; i < choices.length; i++) {
+ let choice = choices[i];
+ if (choice.checked) {return this.toeChoice = choice.value; }
}
}
- title = 'Template Syntax';
// #docregion trackByHeroes
trackByHeroes(index: number, hero: Hero) { return hero.id; }
@@ -196,18 +201,18 @@ export class AppComponent implements AfterViewInit, OnInit {
trackById(index: number, item: any): string { return item['id']; }
// #enddocregion trackById
- val=2;
- // villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
+ val = 2;
+ // villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
// Public Domain terms of use http://www.clker.com/disclaimer.html
- villainImageUrl = 'images/villain.png'
+ villainImageUrl = 'images/villain.png';
//////// Detect effects of NgForTrackBy ///////////////
- @ViewChildren('noTrackBy') childrenNoTrackBy:QueryList;
- @ViewChildren('withTrackBy') childrenWithTrackBy:QueryList;
+ @ViewChildren('noTrackBy') childrenNoTrackBy: QueryList;
+ @ViewChildren('withTrackBy') childrenWithTrackBy: QueryList;
- private _oldNoTrackBy:HTMLElement[];
- private _oldWithTrackBy:HTMLElement[];
+ private _oldNoTrackBy: HTMLElement[];
+ private _oldWithTrackBy: HTMLElement[];
heroesNoTrackByChangeCount = 0;
heroesWithTrackByChangeCount = 0;
@@ -216,32 +221,32 @@ export class AppComponent implements AfterViewInit, OnInit {
this._oldNoTrackBy = toArray(this.childrenNoTrackBy);
this._oldWithTrackBy = toArray(this.childrenWithTrackBy);
- this.childrenNoTrackBy.changes.subscribe((changes:any) => {
+ this.childrenNoTrackBy.changes.subscribe((changes: any) => {
let newNoTrackBy = toArray(changes);
- let isSame = this._oldNoTrackBy.every((v:any, i:number) => v === newNoTrackBy[i]);
+ let isSame = this._oldNoTrackBy.every((v: any, i: number) => v === newNoTrackBy[i]);
if (!isSame) {
this._oldNoTrackBy = newNoTrackBy;
this.heroesNoTrackByChangeCount++;
}
- })
+ });
- this.childrenWithTrackBy.changes.subscribe((changes:any) => {
+ this.childrenWithTrackBy.changes.subscribe((changes: any) => {
let newWithTrackBy = toArray(changes);
- let isSame = this._oldWithTrackBy.every((v:any, i:number) => v === newWithTrackBy[i]);
+ let isSame = this._oldWithTrackBy.every((v: any, i: number) => v === newWithTrackBy[i]);
if (!isSame) {
this._oldWithTrackBy = newWithTrackBy;
this.heroesWithTrackByChangeCount++;
}
- })
+ });
}
///////////////////
}
// helper to convert viewChildren to an array of HTMLElements
-function toArray(viewChildren:QueryList) {
+function toArray(viewChildren: QueryList) {
let result: HTMLElement[] = [];
let children = viewChildren.toArray()[0].nativeElement.children;
- for (var i = 0; i < children.length; i++) { result.push(children[i]); }
+ for (let i = 0; i < children.length; i++) { result.push(children[i]); }
return result;
}
diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade
index 9f1e008a79..dbf8281515 100644
--- a/public/docs/ts/latest/guide/template-syntax.jade
+++ b/public/docs/ts/latest/guide/template-syntax.jade
@@ -568,7 +568,8 @@ a(id="one-time-initialization")
The `[hero]` binding, on the other hand, remains a live binding to the component's `currentHero` property.
### Property binding or interpolation?
- We often have a choice between interpolation and property binding. The following binding pairs do the same thing:
+ We often have a choice between interpolation and property binding.
+ The following binding pairs do the same thing:
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation')(format=".")
:marked
Interpolation is a convenient alternative for property binding in many cases.
@@ -580,6 +581,23 @@ a(id="one-time-initialization")
We suggest establishing coding style rules and choosing the form that
both conforms to the rules and feels most natural for the task at hand.
+
+:marked
+ #### Content Security
+ Imagine the following *malicious content*.
++makeExample('template-syntax/ts/app/app.component.ts', 'evil-title')(format=".")
+:marked
+ Fortunately, Angular data binding is on alert for dangerous HTML.
+ It *sanitizes* the values before displaying them.
+ It **will not** allow HTML with script tags to leak into the browser, neither with interpolation
+ nor property binding.
++makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation-sanitization')(format=".")
+:marked
+ Interpolation handles the script tags differently than property binding but both approaches render the
+ content harmlessly.
+figure.image-display
+ img(src='/resources/images/devguide/template-syntax/evil-title.png' alt="evil title made safe" width='500px')
+
.l-main-section
:marked
diff --git a/public/resources/images/devguide/template-syntax/evil-title.png b/public/resources/images/devguide/template-syntax/evil-title.png
new file mode 100644
index 0000000000..9c67d71894
Binary files /dev/null and b/public/resources/images/devguide/template-syntax/evil-title.png differ