Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions public/docs/_examples/template-syntax/ts/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,18 @@ <h3>
<!-- #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 -->
Expand Down
121 changes: 63 additions & 58 deletions public/docs/_examples/template-syntax/ts/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
}

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

ngOnInit(){
ngOnInit() {
this.refreshHeroes();
}

Expand All @@ -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));
}

// #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:{};
private priorClasses: {};
private _priorStyles: {};

getStyles(el:Element){
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 = '';
Expand All @@ -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 '+ (<HTMLElement>event.target).innerHTML : '';
this.alert('Canceled.'+evtMsg)
onCancel(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
this.alert('Canceled.' + evtMsg);
}

onClickMe(event:KeyboardEvent){
let evtMsg = event ? ' Event target class is '+ (<HTMLElement>event.target).className : '';
this.alert('Click me.'+evtMsg)
onClickMe(event: KeyboardEvent) {
let evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
this.alert('Click me.' + evtMsg);
}

onSave(event:KeyboardEvent){
let evtMsg = event ? ' Event target is '+ (<HTMLElement>event.target).innerText : '';
this.alert('Saved.'+evtMsg)
onSave(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>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 = {
Expand All @@ -123,19 +129,19 @@ 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 ...'};
// });
}
// #enddocregion same-as-it-ever-was

setUpperCaseFirstName(firstName:string){
//console.log(firstName);
setUpperCaseFirstName(firstName: string) {
// console.log(firstName);
this.currentHero.firstName = firstName.toUpperCase();
}

Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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; i<choices.length; i++){
var choice = <HTMLInputElement>choices[i];
if (choice.checked) {return this.toeChoice = choice.value}
for (let i = 0; i < choices.length; i++) {
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; }
Expand All @@ -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<ElementRef>;
@ViewChildren('withTrackBy') childrenWithTrackBy:QueryList<ElementRef>;
@ViewChildren('noTrackBy') childrenNoTrackBy: QueryList<ElementRef>;
@ViewChildren('withTrackBy') childrenWithTrackBy: QueryList<ElementRef>;

private _oldNoTrackBy:HTMLElement[];
private _oldWithTrackBy:HTMLElement[];
private _oldNoTrackBy: HTMLElement[];
private _oldWithTrackBy: HTMLElement[];

heroesNoTrackByChangeCount = 0;
heroesWithTrackByChangeCount = 0;
Expand All @@ -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<ElementRef>) {
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;
}
20 changes: 19 additions & 1 deletion public/docs/ts/latest/guide/template-syntax.jade
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.