docs(template-syntax): add brief example of HTML sanitization in binding.

Also clarified interpolation v. property binding example for PR #1564
This commit is contained in:
Ward Bell 2016-05-31 18:36:22 -07:00
parent 5e50b2ef10
commit ae2d8f2730
4 changed files with 92 additions and 64 deletions

View File

@ -197,13 +197,18 @@ button</button>
<!-- #enddocregion property-binding-7 -->
<!-- #docregion property-binding-vs-interpolation -->
Interpolated: <img src="{{heroImageUrl}}"><br>
Property bound: <img [src]="heroImageUrl">
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<div>The interpolated title is {{title}}</div>
<div [innerHTML]="'The [innerHTML] title is '+title"></div>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
<!-- #enddocregion property-binding-vs-interpolation -->
<!-- #docregion property-binding-vs-interpolation-sanitization -->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
<!-- #enddocregion property-binding-vs-interpolation-sanitization -->
<a class="to-toc" href="#toc">top</a>
<!-- attribute binding -->

View File

@ -1,3 +1,4 @@
/* tslint:disable:member-ordering forin */
// #docplaster
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
@ -40,35 +41,40 @@ 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))
this.alert('Deleted hero: ' + (hero && hero.firstName));
}
// #docregion evil-title
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
// #enddocregion evil-title
title = 'Template Syntax';
// DevMode memoization fields
private priorClasses: {};
private _priorStyles: {};
private _priorStyles2:{};
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[];
@ -89,24 +95,24 @@ export class AppComponent implements AfterViewInit, OnInit {
onCancel(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
this.alert('Canceled.'+evtMsg)
this.alert('Canceled.' + evtMsg);
}
onClickMe(event: KeyboardEvent) {
let evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
this.alert('Click me.'+evtMsg)
this.alert('Click me.' + evtMsg);
}
onSave(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerText : '';
this.alert('Saved.'+evtMsg)
this.alert('Saved.' + evtMsg);
}
onSubmit(form: NgForm) {
let evtMsg = form.valid ?
' Form value is ' + JSON.stringify(form.value) :
' Form is invalid';
this.alert('Form submitted.'+evtMsg)
this.alert('Form submitted.' + evtMsg);
}
product = {
@ -125,8 +131,8 @@ export class AppComponent implements AfterViewInit, OnInit {
private samenessCount = 5;
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 ...'};
@ -145,7 +151,7 @@ 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)) {
@ -165,7 +171,7 @@ 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)) {
@ -181,12 +187,11 @@ export class AppComponent implements AfterViewInit, OnInit {
toeChooser(picker: HTMLFieldSetElement) {
let choices = picker.children;
for (let i = 0; i < choices.length; i++) {
var choice = <HTMLInputElement>choices[i];
if (choice.checked) {return this.toeChoice = choice.value}
let choice = <HTMLInputElement>choices[i];
if (choice.checked) {return this.toeChoice = choice.value; }
}
}
title = 'Template Syntax';
// #docregion trackByHeroes
trackByHeroes(index: number, hero: Hero) { return hero.id; }
@ -199,7 +204,7 @@ export class AppComponent implements AfterViewInit, OnInit {
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 ///////////////
@ -223,7 +228,7 @@ export class AppComponent implements AfterViewInit, OnInit {
this._oldNoTrackBy = newNoTrackBy;
this.heroesNoTrackByChangeCount++;
}
})
});
this.childrenWithTrackBy.changes.subscribe((changes: any) => {
let newWithTrackBy = toArray(changes);
@ -232,7 +237,7 @@ export class AppComponent implements AfterViewInit, OnInit {
this._oldWithTrackBy = newWithTrackBy;
this.heroesWithTrackByChangeCount++;
}
})
});
}
///////////////////
@ -242,6 +247,6 @@ export class AppComponent implements AfterViewInit, OnInit {
function toArray(viewChildren: QueryList<ElementRef>) {
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;
}

View File

@ -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
<a id="other-bindings"></a>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB