Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit ae2d8f2

Browse files
committed
docs(template-syntax): add brief example of HTML sanitization in binding.
Also clarified interpolation v. property binding example for PR #1564
1 parent 5e50b2e commit ae2d8f2

File tree

4 files changed

+91
-63
lines changed

4 files changed

+91
-63
lines changed

public/docs/_examples/template-syntax/ts/app/app.component.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,18 @@ <h3>
197197
<!-- #enddocregion property-binding-7 -->
198198

199199
<!-- #docregion property-binding-vs-interpolation -->
200-
Interpolated: <img src="{{heroImageUrl}}"><br>
201-
Property bound: <img [src]="heroImageUrl">
200+
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
201+
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
202202

203-
<div>The interpolated title is {{title}}</div>
204-
<div [innerHTML]="'The [innerHTML] title is '+title"></div>
203+
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
204+
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
205205
<!-- #enddocregion property-binding-vs-interpolation -->
206206

207+
<!-- #docregion property-binding-vs-interpolation-sanitization -->
208+
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
209+
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
210+
<!-- #enddocregion property-binding-vs-interpolation-sanitization -->
211+
207212
<a class="to-toc" href="#toc">top</a>
208213

209214
<!-- attribute binding -->
Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
//#docplaster
1+
/* tslint:disable:member-ordering forin */
2+
// #docplaster
23

34
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
45
import { NgForm } from '@angular/common';
@@ -8,7 +9,7 @@ import { HeroDetailComponent, BigHeroDetailComponent } from './hero-detail.compo
89
import { MyClickDirective, MyClickDirective2 } from './my-click.directive';
910

1011
// Alerter fn: monkey patch during test
11-
export function alerter(msg?:string) {
12+
export function alerter(msg?: string) {
1213
window.alert(msg);
1314
}
1415

@@ -27,7 +28,7 @@ export enum Color {Red, Green, Blue};
2728
})
2829
export class AppComponent implements AfterViewInit, OnInit {
2930

30-
ngOnInit(){
31+
ngOnInit() {
3132
this.refreshHeroes();
3233
}
3334

@@ -40,43 +41,48 @@ export class AppComponent implements AfterViewInit, OnInit {
4041
badCurly = 'bad curly';
4142
classes = 'special';
4243

43-
callFax(value:string) {this.alert(`Faxing ${value} ...`)}
44-
callPhone(value:string) {this.alert(`Calling ${value} ...`)}
44+
callFax(value: string) {this.alert(`Faxing ${value} ...`); }
45+
callPhone(value: string) {this.alert(`Calling ${value} ...`); }
4546
canSave = true;
4647

4748
Color = Color;
4849
color = Color.Red;
49-
colorToggle() {this.color = (this.color === Color.Red)? Color.Blue : Color.Red}
50+
colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; }
5051

5152
currentHero = Hero.MockHeroes[0];
5253

53-
deleteHero(hero:Hero){
54-
this.alert('Deleted hero: '+ (hero && hero.firstName))
54+
deleteHero(hero: Hero) {
55+
this.alert('Deleted hero: ' + (hero && hero.firstName));
5556
}
5657

58+
// #docregion evil-title
59+
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
60+
// #enddocregion evil-title
61+
62+
title = 'Template Syntax';
63+
5764
// DevMode memoization fields
58-
private priorClasses:{};
59-
private _priorStyles:{};
60-
private _priorStyles2:{};
65+
private priorClasses: {};
66+
private _priorStyles: {};
6167

62-
getStyles(el:Element){
68+
getStyles(el: Element) {
6369
let styles = window.getComputedStyle(el);
6470
let showStyles = {};
65-
for (var p in this.setStyles()){
71+
for (let p in this.setStyles()) {
6672
showStyles[p] = styles[p];
6773
}
6874
return JSON.stringify(showStyles);
6975
}
7076

71-
getVal() {return this.val};
77+
getVal() { return this.val; }
7278

73-
heroes:Hero[];
79+
heroes: Hero[];
7480

7581
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
7682
// Public Domain terms of use: http://www.wpclipart.com/terms.html
7783
heroImageUrl = 'images/hero.png';
7884

79-
//iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
85+
// iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
8086
clicked = '';
8187
clickMessage = '';
8288
clickMessage2 = '';
@@ -85,28 +91,28 @@ export class AppComponent implements AfterViewInit, OnInit {
8591
isSpecial = true;
8692
isUnchanged = true;
8793

88-
nullHero:Hero = null; // or undefined
94+
nullHero: Hero = null; // or undefined
8995

90-
onCancel(event:KeyboardEvent){
91-
let evtMsg = event ? ' Event target is '+ (<HTMLElement>event.target).innerHTML : '';
92-
this.alert('Canceled.'+evtMsg)
96+
onCancel(event: KeyboardEvent) {
97+
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
98+
this.alert('Canceled.' + evtMsg);
9399
}
94100

95-
onClickMe(event:KeyboardEvent){
96-
let evtMsg = event ? ' Event target class is '+ (<HTMLElement>event.target).className : '';
97-
this.alert('Click me.'+evtMsg)
101+
onClickMe(event: KeyboardEvent) {
102+
let evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
103+
this.alert('Click me.' + evtMsg);
98104
}
99105

100-
onSave(event:KeyboardEvent){
101-
let evtMsg = event ? ' Event target is '+ (<HTMLElement>event.target).innerText : '';
102-
this.alert('Saved.'+evtMsg)
106+
onSave(event: KeyboardEvent) {
107+
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerText : '';
108+
this.alert('Saved.' + evtMsg);
103109
}
104110

105-
onSubmit(form:NgForm){
111+
onSubmit(form: NgForm) {
106112
let evtMsg = form.valid ?
107-
' Form value is '+ JSON.stringify(form.value) :
113+
' Form value is ' + JSON.stringify(form.value) :
108114
' Form is invalid';
109-
this.alert('Form submitted.'+evtMsg)
115+
this.alert('Form submitted.' + evtMsg);
110116
}
111117

112118
product = {
@@ -123,19 +129,19 @@ export class AppComponent implements AfterViewInit, OnInit {
123129

124130
// #docregion same-as-it-ever-was
125131
private samenessCount = 5;
126-
moreOfTheSame() {this.samenessCount++;};
132+
moreOfTheSame() { this.samenessCount++; };
127133
get sameAsItEverWas() {
128-
var result:string[] = Array(this.samenessCount);
129-
for (var i=result.length; i-- > 0;){result[i]='same as it ever was ...'}
134+
let result: string[] = Array(this.samenessCount);
135+
for ( let i = result.length; i-- > 0; ) { result[i] = 'same as it ever was ...'; }
130136
return result;
131137
// return [1,2,3,4,5].map(id => {
132138
// return {id:id, text: 'same as it ever was ...'};
133139
// });
134140
}
135141
// #enddocregion same-as-it-ever-was
136142

137-
setUpperCaseFirstName(firstName:string){
138-
//console.log(firstName);
143+
setUpperCaseFirstName(firstName: string) {
144+
// console.log(firstName);
139145
this.currentHero.firstName = firstName.toUpperCase();
140146
}
141147

@@ -145,10 +151,10 @@ export class AppComponent implements AfterViewInit, OnInit {
145151
saveable: this.canSave, // true
146152
modified: !this.isUnchanged, // false
147153
special: this.isSpecial, // true
148-
}
154+
};
149155
// #enddocregion setClasses
150156
// compensate for DevMode (sigh)
151-
if (JSON.stringify(classes) === JSON.stringify(this.priorClasses)){
157+
if (JSON.stringify(classes) === JSON.stringify(this.priorClasses)) {
152158
return this.priorClasses;
153159
}
154160
this.priorClasses = classes;
@@ -165,10 +171,10 @@ export class AppComponent implements AfterViewInit, OnInit {
165171
'font-style': this.canSave ? 'italic' : 'normal', // italic
166172
'font-weight': !this.isUnchanged ? 'bold' : 'normal', // normal
167173
'font-size': this.isSpecial ? '24px' : '8px', // 24px
168-
}
174+
};
169175
// #enddocregion setStyles
170176
// compensate for DevMode (sigh)
171-
if (JSON.stringify(styles) === JSON.stringify(this._priorStyles)){
177+
if (JSON.stringify(styles) === JSON.stringify(this._priorStyles)) {
172178
return this._priorStyles;
173179
}
174180
this._priorStyles = styles;
@@ -178,15 +184,14 @@ export class AppComponent implements AfterViewInit, OnInit {
178184
// #enddocregion setStyles
179185

180186
toeChoice = '';
181-
toeChooser(picker:HTMLFieldSetElement){
187+
toeChooser(picker: HTMLFieldSetElement) {
182188
let choices = picker.children;
183-
for (let i=0; i<choices.length; i++){
184-
var choice = <HTMLInputElement>choices[i];
185-
if (choice.checked) {return this.toeChoice = choice.value}
189+
for (let i = 0; i < choices.length; i++) {
190+
let choice = <HTMLInputElement>choices[i];
191+
if (choice.checked) {return this.toeChoice = choice.value; }
186192
}
187193
}
188194

189-
title = 'Template Syntax';
190195

191196
// #docregion trackByHeroes
192197
trackByHeroes(index: number, hero: Hero) { return hero.id; }
@@ -196,18 +201,18 @@ export class AppComponent implements AfterViewInit, OnInit {
196201
trackById(index: number, item: any): string { return item['id']; }
197202
// #enddocregion trackById
198203

199-
val=2;
200-
// villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
204+
val = 2;
205+
// villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
201206
// Public Domain terms of use http://www.clker.com/disclaimer.html
202-
villainImageUrl = 'images/villain.png'
207+
villainImageUrl = 'images/villain.png';
203208

204209

205210
//////// Detect effects of NgForTrackBy ///////////////
206-
@ViewChildren('noTrackBy') childrenNoTrackBy:QueryList<ElementRef>;
207-
@ViewChildren('withTrackBy') childrenWithTrackBy:QueryList<ElementRef>;
211+
@ViewChildren('noTrackBy') childrenNoTrackBy: QueryList<ElementRef>;
212+
@ViewChildren('withTrackBy') childrenWithTrackBy: QueryList<ElementRef>;
208213

209-
private _oldNoTrackBy:HTMLElement[];
210-
private _oldWithTrackBy:HTMLElement[];
214+
private _oldNoTrackBy: HTMLElement[];
215+
private _oldWithTrackBy: HTMLElement[];
211216

212217
heroesNoTrackByChangeCount = 0;
213218
heroesWithTrackByChangeCount = 0;
@@ -216,32 +221,32 @@ export class AppComponent implements AfterViewInit, OnInit {
216221
this._oldNoTrackBy = toArray(this.childrenNoTrackBy);
217222
this._oldWithTrackBy = toArray(this.childrenWithTrackBy);
218223

219-
this.childrenNoTrackBy.changes.subscribe((changes:any) => {
224+
this.childrenNoTrackBy.changes.subscribe((changes: any) => {
220225
let newNoTrackBy = toArray(changes);
221-
let isSame = this._oldNoTrackBy.every((v:any, i:number) => v === newNoTrackBy[i]);
226+
let isSame = this._oldNoTrackBy.every((v: any, i: number) => v === newNoTrackBy[i]);
222227
if (!isSame) {
223228
this._oldNoTrackBy = newNoTrackBy;
224229
this.heroesNoTrackByChangeCount++;
225230
}
226-
})
231+
});
227232

228-
this.childrenWithTrackBy.changes.subscribe((changes:any) => {
233+
this.childrenWithTrackBy.changes.subscribe((changes: any) => {
229234
let newWithTrackBy = toArray(changes);
230-
let isSame = this._oldWithTrackBy.every((v:any, i:number) => v === newWithTrackBy[i]);
235+
let isSame = this._oldWithTrackBy.every((v: any, i: number) => v === newWithTrackBy[i]);
231236
if (!isSame) {
232237
this._oldWithTrackBy = newWithTrackBy;
233238
this.heroesWithTrackByChangeCount++;
234239
}
235-
})
240+
});
236241
}
237242
///////////////////
238243

239244
}
240245

241246
// helper to convert viewChildren to an array of HTMLElements
242-
function toArray(viewChildren:QueryList<ElementRef>) {
247+
function toArray(viewChildren: QueryList<ElementRef>) {
243248
let result: HTMLElement[] = [];
244249
let children = viewChildren.toArray()[0].nativeElement.children;
245-
for (var i = 0; i < children.length; i++) { result.push(children[i]); }
250+
for (let i = 0; i < children.length; i++) { result.push(children[i]); }
246251
return result;
247252
}

public/docs/ts/latest/guide/template-syntax.jade

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,8 @@ a(id="one-time-initialization")
568568
The `[hero]` binding, on the other hand, remains a live binding to the component's `currentHero` property.
569569

570570
### Property binding or interpolation?
571-
We often have a choice between interpolation and property binding. The following binding pairs do the same thing:
571+
We often have a choice between interpolation and property binding.
572+
The following binding pairs do the same thing:
572573
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation')(format=".")
573574
:marked
574575
Interpolation is a convenient alternative for property binding in many cases.
@@ -580,6 +581,23 @@ a(id="one-time-initialization")
580581
We suggest establishing coding style rules and choosing the form that
581582
both conforms to the rules and feels most natural for the task at hand.
582583

584+
585+
:marked
586+
#### Content Security
587+
Imagine the following *malicious content*.
588+
+makeExample('template-syntax/ts/app/app.component.ts', 'evil-title')(format=".")
589+
:marked
590+
Fortunately, Angular data binding is on alert for dangerous HTML.
591+
It *sanitizes* the values before displaying them.
592+
It **will not** allow HTML with script tags to leak into the browser, neither with interpolation
593+
nor property binding.
594+
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation-sanitization')(format=".")
595+
:marked
596+
Interpolation handles the script tags differently than property binding but both approaches render the
597+
content harmlessly.
598+
figure.image-display
599+
img(src='/resources/images/devguide/template-syntax/evil-title.png' alt="evil title made safe" width='500px')
600+
583601
.l-main-section
584602
:marked
585603
<a id="other-bindings"></a>
31.5 KB
Loading

0 commit comments

Comments
 (0)