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