Merge remote-tracking branch 'origin/master'

# Conflicts:
#	README.md
#	public/docs/ts/latest/_data.json
#	public/docs/ts/latest/cookbook/_data.json
#	public/docs/ts/latest/cookbook/dynamic-form.jade
#	public/docs/ts/latest/guide/_data.json
#	public/docs/ts/latest/guide/forms.jade
#	public/docs/ts/latest/guide/style-guide.jade
This commit is contained in:
Zhicheng Wang 2016-06-23 08:48:57 +08:00
commit bb9adc3eba
130 changed files with 3509 additions and 483 deletions

View File

@ -7,6 +7,7 @@ os:
- linux
env:
global:
- DBUS_SESSION_BUS_ADDRESS=/dev/null
- DISPLAY=:99.0
- CHROME_BIN=chromium-browser
matrix:

View File

@ -37,7 +37,7 @@ var DOCS_PATH = path.join(PUBLIC_PATH, 'docs');
var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples');
var EXAMPLES_PROTRACTOR_PATH = path.join(EXAMPLES_PATH, '_protractor');
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*');
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*.*');
var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources');
var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples');
@ -991,6 +991,7 @@ function devGuideExamplesWatch(shredOptions, postShredAction) {
// gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) {
var ignoreThese = [ '**/node_modules/**', '**/_fragments/**', '**/dist/**', '**/typings/**',
'**/dart/.pub/**', '**/dart/build/**', '**/dart/packages/**'];
ignoreThese = ignoreThese.concat(_exampleBoilerplateFiles.map((file) => `public/docs/_examples/*/*/${file}`));
var files = globby.sync( [includePattern], { ignore: ignoreThese });
gulp.watch([files], {readDelay: 500}, function (event, done) {
gutil.log('Dev Guide example changed')
@ -1005,7 +1006,8 @@ function devGuideSharedJadeWatch(shredOptions, postShredAction) {
// removed this version because gulp.watch has the same glob issue that dgeni has.
// var excludePattern = '!' + path.join(shredOptions.jadeDir, '**/node_modules/**/*.*');
// gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) {
var files = globby.sync( [includePattern], { ignore: [ '**/node_modules/**', '**/_examples/**', '**/_fragments/**']});
var ignoreThese = [ '**/node_modules/**', '**/_examples/**', '**/_fragments/**', '**/latest/api/**' ];
var files = globby.sync( [includePattern], { ignore: ignoreThese});
gulp.watch([files], {readDelay: 500}, function (event, done) {
gutil.log('Dev Guide jade file changed')
gutil.log('Event type: ' + event.type); // added, changed, or deleted

View File

@ -88,14 +88,6 @@
"type": "Google"
},
"brianford": {
"name": "Brian Ford",
"picture": "/resources/images/bios/brian-ford.jpg",
"twitter": "briantford",
"bio": "Brian works on the AngularJS core team at Google where he tries his very best to make computers do the right thing.",
"type": "Google"
},
"rado": {
"name": "Rado Kirov",
"picture": "/resources/images/bios/rado.jpg",
@ -294,6 +286,15 @@
"bio": "Stephen is a Developer Advocate working on the Angular team. Before joining Google, he was a Google Expert. Stephen loves to help enterprises use technology more effectively.",
"type": "Google"
},
"robwormald": {
"name": "Rob Wormald",
"picture": "/resources/images/bios/rob-wormald.jpg",
"twitter": "robwormald",
"website": "http://github.com/robwormald",
"bio": "Rob is a Developer Advocate on the Angular team at Google. He's the Angular team's resident reactive programming geek and founded the Reactive Extensions for Angular project, ngrx.",
"type": "Google"
},
"pawel": {
"name": "Pawel Kozlowski",
"picture": "/resources/images/bios/pawel.jpg",
@ -422,6 +423,30 @@
"website": "http://angular-tips.com",
"bio": "Jesus is an open source lover, a book author and editor, and AngularUI lead developer. He is currently a core contributor to the UI Bootstrap project.",
"type": "Community"
},
"torgeirhelgevold": {
"name": "Torgeir Helgevold",
"picture": "/resources/images/bios/torgeirhelgevold.jpg",
"twitter": "helgevold",
"website": "http://www.syntaxsuccess.com",
"bio": "Torgeir (Tor) is a front-end architect with a passion for JavaScript development. He is also an author for angular.io and an active tech blogger.",
"type": "Community"
},
"fatimaremtullah": {
"name": "Fatima Remtullah",
"picture": "/resources/images/bios/fatima.jpg",
"twitter": "amitafr",
"website": "http://www.amitafremtullah.com",
"bio": "Fatima is a Product Designer and Front-End Developer. When she is not nerding out she is probably eating an abundance of cookies.",
"type": "Community"
},
"eric": {
"name": "Eric Jimenez",
"picture": "/resources/images/bios/eric.jpg",
"twitter": "_ericjim",
"website": "http://eric.to/",
"bio": "Eric is a gamer, writer, and programmer.",
"type": "Community"
}
}
}

View File

@ -1,8 +1,8 @@
{
"globalDependencies": {
"angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459",
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
"node": "registry:dt/node#6.0.0+20160613154055",
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
"node": "registry:dt/node#6.0.0+20160621231320",
"selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654"
}
}

View File

@ -0,0 +1,27 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
/* tslint:disable:quotemark */
describe('Dynamic Form Deprecated', function () {
beforeAll(function () {
browser.get('');
});
it('should submit form', function () {
let firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
let emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
let email = 'test@test.com';
emailElement.sendKeys(email);
expect(emailElement.getAttribute('value')).toEqual(email);
element(by.css('select option[value="solid"]')).click();
let saveButton = element.all(by.css('button')).get(0);
saveButton.click().then(function(){
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
});
});
});

View File

@ -0,0 +1,24 @@
// #docregion
import { Component } from '@angular/core';
import { DynamicFormComponent } from './dynamic-form.component';
import { QuestionService } from './question.service';
@Component({
selector: 'my-app',
template: `
<div>
<h2>Job Application for Heroes</h2>
<dynamic-form [questions]="questions"></dynamic-form>
</div>
`,
directives: [DynamicFormComponent],
providers: [QuestionService]
})
export class AppComponent {
questions: any[];
constructor(service: QuestionService) {
this.questions = service.getQuestions();
}
}

View File

@ -0,0 +1,17 @@
<!-- #docregion -->
<div [ngFormModel]="form">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchWhen="'textbox'" [ngControl]="question.key"
[id]="question.key" [type]="question.type">
<select [id]="question.key" *ngSwitchWhen="'dropdown'" [ngControl]="question.key">
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>

View File

@ -0,0 +1,15 @@
// #docregion
import { Component, Input } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
@Component({
selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html'
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@Input() form: ControlGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}

View File

@ -0,0 +1,17 @@
<!-- #docregion -->
<div>
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
<div *ngFor="let question of questions" class="form-row">
<df-question [question]="question" [form]="form"></df-question>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>

View File

@ -0,0 +1,30 @@
// #docregion
import { Component, Input, OnInit } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
@Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [DynamicFormQuestionComponent],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
@Input() questions: QuestionBase<any>[] = [];
form: ControlGroup;
payLoad = '';
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toControlGroup(this.questions);
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
}
}

View File

@ -0,0 +1,6 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent, [])
.catch((err: any) => console.error(err));

View File

@ -0,0 +1,25 @@
// #docregion
export class QuestionBase<T>{
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;
constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}

View File

@ -0,0 +1,18 @@
// #docregion
import { Injectable } from '@angular/core';
import { FormBuilder, Validators } from '@angular/common';
import { QuestionBase } from './question-base';
@Injectable()
export class QuestionControlService {
constructor(private fb: FormBuilder) { }
toControlGroup(questions: QuestionBase<any>[] ) {
let group = {};
questions.forEach(question => {
group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || ''];
});
return this.fb.group(group);
}
}

View File

@ -0,0 +1,12 @@
// #docregion
import { QuestionBase } from './question-base';
export class DropdownQuestion extends QuestionBase<string> {
controlType = 'dropdown';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}

View File

@ -0,0 +1,12 @@
// #docregion
import { QuestionBase } from './question-base';
export class TextboxQuestion extends QuestionBase<string> {
controlType = 'textbox';
type: string;
constructor(options: {} = {}) {
super(options);
this.type = options['type'] || '';
}
}

View File

@ -0,0 +1,47 @@
// #docregion
import { Injectable } from '@angular/core';
import { QuestionBase } from './question-base';
import { TextboxQuestion } from './question-textbox';
import { DropdownQuestion } from './question-dropdown';
@Injectable()
export class QuestionService {
// Todo: get from a remote source of question metadata
// Todo: make asynchronous
getQuestions() {
let questions: QuestionBase<any>[] = [
new DropdownQuestion({
key: 'brave',
label: 'Bravery Rating',
options: [
{key: 'solid', value: 'Solid'},
{key: 'great', value: 'Great'},
{key: 'good', value: 'Good'},
{key: 'unproven', value: 'Unproven'}
],
order: 3
}),
new TextboxQuestion({
key: 'firstName',
label: 'First name',
value: 'Bombasto',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'emailAddress',
label: 'Email',
type: 'email',
order: 2
})
];
return questions.sort((a, b) => a.order - b.order);
}
}

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<base href="/">
<title>Dynamic Form</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion style -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- #enddocregion style -->
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading app...</my-app>
</body>
</html>

View File

@ -0,0 +1,9 @@
{
"description": "Dynamic Form Deprecated",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}

View File

@ -0,0 +1,7 @@
.errorMessage{
color:red;
}
.form-row{
margin-top: 10px;
}

View File

@ -1,17 +1,17 @@
<!-- #docregion -->
<div [ngFormModel]="form">
<div [formGroup]="form">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchWhen="'textbox'" [ngControl]="question.key"
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
[id]="question.key" [type]="question.type">
<select [id]="question.key" *ngSwitchWhen="'dropdown'" [ngControl]="question.key">
<select [id]="question.key" *ngSwitchCase="'dropdown'" [formControlName]="question.key">
<option *ngFor="let opt of question.options" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
</div>
<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>

View File

@ -1,15 +1,16 @@
// #docregion
import { Component, Input } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms';
import { QuestionBase } from './question-base';
@Component({
selector: 'df-question',
templateUrl: 'app/dynamic-form-question.component.html'
templateUrl: 'app/dynamic-form-question.component.html',
directives: [REACTIVE_FORM_DIRECTIVES]
})
export class DynamicFormQuestionComponent {
@Input() question: QuestionBase<any>;
@Input() form: ControlGroup;
@Input() form: FormGroup;
get isValid() { return this.form.controls[this.question.key].valid; }
}

View File

@ -1,6 +1,6 @@
<!-- #docregion -->
<div>
<form (ngSubmit)="onSubmit()" [ngFormModel]="form">
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<df-question [question]="question" [form]="form"></df-question>

View File

@ -1,27 +1,27 @@
// #docregion
import { Component, Input, OnInit } from '@angular/core';
import { ControlGroup } from '@angular/common';
import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
@Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [DynamicFormQuestionComponent],
directives: [DynamicFormQuestionComponent, REACTIVE_FORM_DIRECTIVES],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
@Input() questions: QuestionBase<any>[] = [];
form: ControlGroup;
form: FormGroup;
payLoad = '';
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toControlGroup(this.questions);
this.form = this.qcs.toFormGroup(this.questions);
}
onSubmit() {

View File

@ -1,6 +1,11 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
// #docregion
import { bootstrap } from '@angular/platform-browser-dynamic';
import { disableDeprecatedForms, provideForms } from '@angular/forms';
import { AppComponent } from './app.component';
bootstrap(AppComponent, [])
.catch((err: any) => console.error(err));
bootstrap(AppComponent, [
disableDeprecatedForms(),
provideForms()
])
.catch((err: any) => console.error(err));

View File

@ -1,18 +1,20 @@
// #docregion
import { Injectable } from '@angular/core';
import { FormBuilder, Validators } from '@angular/common';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { QuestionBase } from './question-base';
@Injectable()
export class QuestionControlService {
constructor(private fb: FormBuilder) { }
constructor() { }
toControlGroup(questions: QuestionBase<any>[] ) {
let group = {};
toFormGroup(questions: QuestionBase<any>[] ) {
let group: any = {};
questions.forEach(question => {
group[question.key] = question.required ? [question.value || '', Validators.required] : [question.value || ''];
group[question.key] = question.required ? new FormControl(question.value || '', Validators.required)
: new FormControl(question.value || '');
});
return this.fb.group(group);
return new FormGroup(group);
}
}

View File

@ -1,9 +1,9 @@
// #docregion
import { Injectable } from '@angular/core';
import { DropdownQuestion } from './question-dropdown';
import { QuestionBase } from './question-base';
import { TextboxQuestion } from './question-textbox';
import { DropdownQuestion } from './question-dropdown';
@Injectable()
export class QuestionService {

View File

@ -0,0 +1,64 @@
/// <reference path='../_protractor/e2e.d.ts' />
'use strict';
describeIf(browser.appIsTs || browser.appIsJs, 'Forms (Deprecated) Tests', function () {
beforeEach(function () {
browser.get('');
});
it('should display correct title', function () {
expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
});
it('should not display message before submit', function () {
let ele = element(by.css('h2'));
expect(ele.isDisplayed()).toBe(false);
});
it('should hide form after submit', function () {
let ele = element.all(by.css('h1')).get(0);
expect(ele.isDisplayed()).toBe(true);
let b = element.all(by.css('button[type=submit]')).get(0);
b.click().then(function() {
expect(ele.isDisplayed()).toBe(false);
});
});
it('should display message after submit', function () {
let b = element.all(by.css('button[type=submit]')).get(0);
b.click().then(function() {
expect(element(by.css('h2')).getText()).toContain('You submitted the following');
});
});
it('should hide form after submit', function () {
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
expect(alterEgoEle.isDisplayed()).toBe(true);
let submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
submitButtonEle.click().then(function() {
expect(alterEgoEle.isDisplayed()).toBe(false);
});
});
it('should reflect submitted data after submit', function () {
let test = 'testing 1 2 3';
let newValue: string;
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
alterEgoEle.getAttribute('value').then(function(value) {
// alterEgoEle.sendKeys(test);
sendKeys(alterEgoEle, test);
newValue = value + test;
expect(alterEgoEle.getAttribute('value')).toEqual(newValue);
}).then(function() {
let b = element.all(by.css('button[type=submit]')).get(0);
return b.click();
}).then(function() {
let alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
let divEle = element(by.cssContainingText('div', newValue));
expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue);
});
});
});

View File

@ -0,0 +1,12 @@
// #docregion
(function(app) {
app.AppComponent = ng.core
.Component({
selector: 'my-app',
template: '<hero-form></hero-form>',
directives: [app.HeroFormComponent]
})
.Class({
constructor: function() {}
});
})(window.app || (window.app = {}));

View File

@ -0,0 +1,195 @@
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
<!-- #docregion edit-div -->
<div [hidden]="submitted">
<h1>Hero Form</h1>
<!-- #docregion ngSubmit -->
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<!-- #enddocregion ngSubmit -->
<!-- #enddocregion edit-div -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo"
ngControl="alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div [hidden]="power.valid" class="alert alert-danger">
Power is required
</div>
</div>
<!-- #docregion submit-button -->
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.form.valid">Submit</button>
<!-- #enddocregion submit-button -->
</form>
</div>
<!-- #docregion submitted -->
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
<!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->
<!-- ==================================================== -->
<div>
<form>
<!-- #docregion edit-div -->
<!-- ... all of the form ... -->
</form>
</div>
<!-- #enddocregion edit-div -->
<!-- ==================================================== -->
<hr>
<style>
.no-style .ng-valid {
border-left: 1px solid #CCC
}
.no-style .ng-invalid {
border-left: 1px solid #CCC
}
</style>
<div class="no-style" style="margin-left: 4px">
<!-- #docregion start -->
<div class="container">
<h1>Hero Form</h1>
<form>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control">
</div>
<!-- #enddocregion start -->
<!-- #docregion powers -->
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required>
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion powers -->
<!-- #docregion start -->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion start -->
<!-- #enddocregion phase1-->
<!-- ==================================================== -->
<hr>
<!-- #docregion phase2-->
<div class="container">
<h1>Hero Form</h1>
<form>
<!-- #docregion ngModel-2-->
{{diagnostic()}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion ngModel-2-->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion phase2-->
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
<hr>
<!-- #docregion ngModel-1-->
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-1-->
<hr>
<!-- #docregion ngModel-3-->
<input type="text" class="form-control" required
[ngModel]="model.name"
(ngModelChange)="model.name = $event" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-3-->
<hr>
<form>
<!-- #docregion ngControl-1 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" >
<!-- #enddocregion ngControl-1 -->
<hr>
<!-- #docregion ngControl-2 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #spy >
<br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ngControl-2 -->
</form>
<div>
<hr>
Name via form.controls = {{showFormControls(heroForm)}}
</div>
</div>

View File

@ -0,0 +1,52 @@
// #docplaster
// #docregion
// #docregion first, final
(function(app) {
app.HeroFormComponent = ng.core
.Component({
selector: 'hero-form',
templateUrl: 'app/hero-form.component.html'
})
.Class({
// #docregion submitted
constructor: function() {
// #enddocregion submitted
this.powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'
];
this.model = new app.Hero(18, 'Dr IQ', this.powers[0],
'Chuck Overstreet');
// #docregion submitted
this.submitted = false;
},
onSubmit: function() {
this.submitted = true;
},
// #enddocregion submitted
// #enddocregion final
// TODO: Remove this when we're done
diagnostic: function() {
return JSON.stringify(this.model);
},
// #enddocregion first
//////// DO NOT SHOW IN DOCS ////////
// Reveal in html:
// AlterEgo via form.controls = {{showFormControls(hf)}}
showFormControls: function(form) {
return form.controls['alterEgo'] &&
// #docregion form-controls
form.controls['name'].value; // Dr. IQ
// #enddocregion form-controls
},
/////////////////////////////
// #docregion first, final
});
// #enddocregion first, final
})(window.app || (window.app = {}));

View File

@ -0,0 +1,11 @@
// #docregion
(function(app) {
app.Hero = Hero;
function Hero(id, name, power, alterEgo) {
this.id = id;
this.name = name;
this.power = power;
this.alterEgo = alterEgo;
}
})(window.app || (window.app = {}));

View File

@ -0,0 +1,6 @@
// #docregion
(function(app) {
document.addEventListener('DOMContentLoaded', function() {
ng.platformBrowserDynamic.bootstrap(app.AppComponent);
});
})(window.app || (window.app = {}));

View File

@ -0,0 +1,9 @@
/* #docregion */
.ng-valid[required] {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
border-left: 5px solid #a94442; /* red */
}
/* #enddocregion */

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<!-- #docplaster -->
<!-- #docregion -->
<html>
<head>
<title>Hero Form</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<!-- #enddocregion bootstrap -->
<!-- #docregion styles -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="forms.css">
<!-- #enddocregion styles -->
<!-- IE required polyfill -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/rxjs/bundles/Rx.umd.js"></script>
<script src="node_modules/@angular/core/bundles/core.umd.js"></script>
<script src="node_modules/@angular/common/bundles/common.umd.js"></script>
<script src="node_modules/@angular/compiler/bundles/compiler.umd.js"></script>
<script src="node_modules/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
<script src="node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
<!-- #docregion scripts-hero, scripts-hero-form -->
<script src='app/hero.js'></script>
<!-- #enddocregion scripts-hero -->
<script src='app/hero-form.component.js'></script>
<!-- #enddocregion scripts-hero-form -->
<!-- #docregion scripts, scripts-hero, scripts-hero-form -->
<script src='app/app.component.js'></script>
<script src='app/main.js'></script>
<!-- #enddocregion scripts, scripts-hero, scripts-hero-form -->
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,4 @@
{
"description": "Forms",
"files":["app/**/*.js", "**/*.html", "**/*.css"]
}

View File

@ -0,0 +1,10 @@
// #docregion
import { Component } from '@angular/core';
import { HeroFormComponent } from './hero-form.component';
@Component({
selector: 'my-app',
template: '<hero-form></hero-form>',
directives: [HeroFormComponent]
})
export class AppComponent { }

View File

@ -0,0 +1,208 @@
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
<!-- #docregion edit-div -->
<div [hidden]="submitted">
<h1>Hero Form</h1>
<!-- #docregion ngSubmit -->
<form *ngIf="active" (ngSubmit)="onSubmit()" #heroForm="ngForm">
<!-- #enddocregion ngSubmit -->
<!-- #enddocregion edit-div -->
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
<!-- #docregion hidden-error-msg -->
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
<!-- #enddocregion hidden-error-msg -->
Name is required
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo"
ngControl="alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div [hidden]="power.valid || power.pristine" class="alert alert-danger">
Power is required
</div>
</div>
<!-- #docregion submit-button -->
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>
<!-- #enddocregion submit-button -->
<!-- #docregion new-hero-button -->
<button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
<!-- #enddocregion new-hero-button -->
<!-- #enddocregion final -->
<!-- NOT SHOWN IN DOCS -->
<div>
<hr>
Name via form.controls = {{showFormControls(heroForm)}}
</div>
<!-- - -->
<!-- #docregion final -->
</form>
</div>
<!-- #docregion submitted -->
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
<!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->
<!-- ==================================================== -->
<div>
<form>
<!-- #docregion edit-div -->
<!-- ... all of the form ... -->
</form>
</div>
<!-- #enddocregion edit-div -->
<!-- ==================================================== -->
<hr>
<style>
.no-style .ng-valid {
border-left: 1px solid #CCC
}
.no-style .ng-invalid {
border-left: 1px solid #CCC
}
</style>
<div class="no-style" style="margin-left: 4px">
<!-- #docregion start -->
<div class="container">
<h1>Hero Form</h1>
<form>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control">
</div>
<!-- #enddocregion start -->
<!-- #docregion powers -->
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required>
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion powers -->
<!-- #docregion start -->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion start -->
<!-- #enddocregion phase1-->
<!-- ==================================================== -->
<hr>
<!-- #docregion phase2-->
<div class="container">
<h1>Hero Form</h1>
<form>
<!-- #docregion ngModel-2-->
{{diagnostic}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion ngModel-2-->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion phase2-->
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
<hr>
<!-- #docregion ngModel-1-->
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-1-->
<hr>
<!-- #docregion ngModel-3-->
<input type="text" class="form-control" required
[ngModel]="model.name"
(ngModelChange)="model.name = $event" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-3-->
<hr>
<!-- #docregion form-active -->
<form *ngIf="active">
<!-- #enddocregion form-active -->
<!-- #docregion ngControl-1 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" >
<!-- #enddocregion ngControl-1 -->
<hr>
<!-- #docregion ngControl-2 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #spy >
<br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ngControl-2 -->
</form>
</div>

View File

@ -0,0 +1,66 @@
// #docplaster
// #docregion
// #docregion first, final
import { Component } from '@angular/core';
import { NgForm } from '@angular/common';
import { Hero } from './hero';
@Component({
selector: 'hero-form',
templateUrl: 'app/hero-form.component.html'
})
export class HeroFormComponent {
powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
// #docregion submitted
submitted = false;
onSubmit() { this.submitted = true; }
// #enddocregion submitted
// #enddocregion final
// TODO: Remove this when we're done
get diagnostic() { return JSON.stringify(this.model); }
// #enddocregion first
// #docregion final
// Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822)
// #docregion new-hero
active = true;
// #docregion new-hero-v1
newHero() {
this.model = new Hero(42, '', '');
// #enddocregion new-hero-v1
this.active = false;
setTimeout(() => this.active = true, 0);
// #docregion new-hero-v1
}
// #enddocregion new-hero-v1
// #enddocregion new-hero
// #enddocregion final
//////// NOT SHOWN IN DOCS ////////
// Reveal in html:
// Name via form.controls = {{showFormControls(heroForm)}}
showFormControls(form: NgForm) {
return form && form.controls['name'] &&
// #docregion form-controls
form.controls['name'].value; // Dr. IQ
// #enddocregion form-controls
}
/////////////////////////////
// #docregion first, final
}
// #enddocregion first, final

View File

@ -0,0 +1,11 @@
// #docregion
export class Hero {
constructor(
public id: number,
public name: string,
public power: string,
public alterEgo?: string
) { }
}

View File

@ -0,0 +1,6 @@
// #docregion
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent);

View File

@ -0,0 +1,9 @@
/* #docregion */
.ng-valid[required] {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
border-left: 5px solid #a94442; /* red */
}
/* #enddocregion */

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Hero Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<!-- #enddocregion bootstrap -->
<!-- #docregion styles -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="forms.css">
<!-- #enddocregion styles -->
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,7 @@
{
"description": "Forms-Deprecated",
"files":[
"!**/*.d.ts",
"!**/*.js"
]
}

View File

@ -33,7 +33,7 @@ describeIf(browser.appIsTs || browser.appIsJs, 'Forms Tests', function () {
});
it('should hide form after submit', function () {
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
expect(alterEgoEle.isDisplayed()).toBe(true);
let submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
submitButtonEle.click().then(function() {
@ -44,7 +44,7 @@ describeIf(browser.appIsTs || browser.appIsJs, 'Forms Tests', function () {
it('should reflect submitted data after submit', function () {
let test = 'testing 1 2 3';
let newValue: string;
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
alterEgoEle.getAttribute('value').then(function(value) {
// alterEgoEle.sendKeys(test);
sendKeys(alterEgoEle, test);

View File

@ -13,7 +13,7 @@
<!-- #docregion name-with-error-msg -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
name="name" #name="ngModel" >
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
@ -24,14 +24,14 @@
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo"
ngControl="alterEgo" >
name="alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
name="power" #power="ngModel" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div [hidden]="power.valid" class="alert alert-danger">
@ -133,19 +133,19 @@
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
[(ngModel)]="model.name" name="name" >
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo">
[(ngModel)]="model.alterEgo" name="alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power" >
[(ngModel)]="model.power" name="power">
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
@ -173,18 +173,18 @@
<!-- #enddocregion ngModel-3-->
<hr>
<form>
<!-- #docregion ngControl-1 -->
<!-- #docregion ngModelName-1 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" >
<!-- #enddocregion ngControl-1 -->
name="name" >
<!-- #enddocregion ngModelName-1 -->
<hr>
<!-- #docregion ngControl-2 -->
<!-- #docregion ngModelName-2 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #spy >
name="name" #spy >
<br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ngControl-2 -->
<!-- #enddocregion ngModelName-2 -->
</form>
<div>

View File

@ -1,6 +1,9 @@
// #docregion
(function(app) {
document.addEventListener('DOMContentLoaded', function() {
ng.platformBrowserDynamic.bootstrap(app.AppComponent);
ng.platformBrowserDynamic.bootstrap(app.AppComponent,[
ng.forms.disableDeprecatedForms(),
ng.forms.provideForms()
]);
});
})(window.app || (window.app = {}));

View File

@ -24,6 +24,7 @@
<script src="node_modules/@angular/core/bundles/core.umd.js"></script>
<script src="node_modules/@angular/common/bundles/common.umd.js"></script>
<script src="node_modules/@angular/compiler/bundles/compiler.umd.js"></script>
<script src="node_modules/@angular/forms/bundles/forms.umd.js"></script>
<script src="node_modules/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
<script src="node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>

View File

@ -13,7 +13,7 @@
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
name="name" #name="ngModel" >
<!-- #docregion hidden-error-msg -->
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
<!-- #enddocregion hidden-error-msg -->
@ -26,14 +26,14 @@
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo"
ngControl="alterEgo" >
name="alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
name="power" #power="ngModel" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div [hidden]="power.valid || power.pristine" class="alert alert-danger">
@ -148,19 +148,19 @@
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
[(ngModel)]="model.name" name="name">
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ngModel)]="model.alterEgo">
[(ngModel)]="model.alterEgo" name="alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ngModel)]="model.power" >
[(ngModel)]="model.power" name="power">
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
</div>
@ -176,7 +176,7 @@
<hr>
<!-- #docregion ngModel-1-->
<input type="text" class="form-control" required
[(ngModel)]="model.name" >
[(ngModel)]="model.name" name="name">
TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-1-->
<hr>
@ -191,18 +191,18 @@
<form *ngIf="active">
<!-- #enddocregion form-active -->
<!-- #docregion ngControl-1 -->
<!-- #docregion ngModelName-1 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" >
<!-- #enddocregion ngControl-1 -->
name="name" >
<!-- #enddocregion ngModelName-1 -->
<hr>
<!-- #docregion ngControl-2 -->
<!-- #docregion ngModelName-2 -->
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #spy >
name="name" #spy >
<br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ngControl-2 -->
<!-- #enddocregion ngModelName-2 -->
</form>
</div>

View File

@ -1,6 +1,11 @@
// #docregion
import { bootstrap } from '@angular/platform-browser-dynamic';
import { disableDeprecatedForms, provideForms } from '@angular/forms';
import { AppComponent } from './app.component';
bootstrap(AppComponent);
bootstrap(AppComponent, [
disableDeprecatedForms(),
provideForms()
])
.catch((err: any) => console.error(err));

View File

@ -25,15 +25,16 @@
"author": "",
"license": "ISC",
"dependencies": {
"@angular/common": "2.0.0-rc.2",
"@angular/compiler": "2.0.0-rc.2",
"@angular/core": "2.0.0-rc.2",
"@angular/http": "2.0.0-rc.2",
"@angular/platform-browser": "2.0.0-rc.2",
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
"@angular/router": "2.0.0-rc.2",
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "0.1.1",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"@angular/router": "3.0.0-alpha.7",
"@angular/router-deprecated": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.3",
"angular2-in-memory-web-api": "0.0.12",
"bootstrap": "^3.3.6",
"core-js": "^2.4.0",

View File

@ -7,15 +7,16 @@
},
"license": "ISC",
"dependencies": {
"@angular/common": "2.0.0-rc.2",
"@angular/compiler": "2.0.0-rc.2",
"@angular/core": "2.0.0-rc.2",
"@angular/http": "2.0.0-rc.2",
"@angular/platform-browser": "2.0.0-rc.2",
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
"@angular/router": "2.0.0-rc.2",
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "0.1.1",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"@angular/router": "3.0.0-alpha.7",
"@angular/router-deprecated": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.3",
"core-js": "^2.4.0",
"reflect-metadata": "0.1.3",

View File

@ -0,0 +1,14 @@
/* #docregion */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
body {
margin: 2em;
}
/*
* See https://github.com/angular/angular.io/blob/master/public/docs/_examples/styles.css
* for the full set of master styles used by the documentation samples
*/

View File

@ -11,15 +11,16 @@
},
"license": "ISC",
"dependencies": {
"@angular/common": "2.0.0-rc.2",
"@angular/compiler": "2.0.0-rc.2",
"@angular/core": "2.0.0-rc.2",
"@angular/http": "2.0.0-rc.2",
"@angular/platform-browser": "2.0.0-rc.2",
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
"@angular/router": "2.0.0-rc.2",
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "0.1.1",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"@angular/router": "3.0.0-alpha.7",
"@angular/router-deprecated": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.3",
"systemjs": "0.19.27",
"core-js": "^2.4.0",

View File

@ -25,6 +25,7 @@
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',

View File

@ -1,7 +1,7 @@
{
"globalDependencies": {
"core-js": "registry:dt/core-js#0.0.0+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
"node": "registry:dt/node#6.0.0+20160613154055"
"core-js": "registry:dt/core-js#0.0.0+20160602141332",
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
"node": "registry:dt/node#6.0.0+20160621231320"
}
}

View File

@ -24,14 +24,18 @@ describe('Router', function () {
heroDetail: element(by.css('my-app > undefined > div')),
heroDetailTitle: element(by.css('my-app > undefined > div > h3')),
adminHref: hrefEles.get(2),
loginHref: hrefEles.get(3)
};
}
it('should be able to see the start screen', function () {
let page = getPageStruct();
expect(page.hrefs.count()).toEqual(2, 'should be two dashboard choices');
expect(page.hrefs.count()).toEqual(4, 'should be two dashboard choices');
expect(page.crisisHref.getText()).toEqual('Crisis Center');
expect(page.heroesHref.getText()).toEqual('Heroes');
expect(page.adminHref.getText()).toEqual('Crisis Admin');
expect(page.loginHref.getText()).toEqual('Login');
});
it('should be able to see crises center items', function () {

View File

@ -3,7 +3,7 @@
// #docregion
import { Component } from '@angular/core';
import { Router, ROUTER_DIRECTIVES } from '@angular/router';
import { ROUTER_DIRECTIVES } from '@angular/router';
// #enddocregion
/*

View File

@ -16,11 +16,11 @@ import { HeroDetailComponent } from './heroes/hero-detail.component';
// #docregion route-config
export const routes: RouterConfig = [
// #docregion route-defs
{ path: '/crisis-center', component: CrisisCenterComponent },
{ path: '/heroes', component: HeroListComponent },
{ path: 'crisis-center', component: CrisisCenterComponent },
{ path: 'heroes', component: HeroListComponent },
// #enddocregion route-defs
// #docregion hero-detail-route
{ path: '/hero/:id', component: HeroDetailComponent }
{ path: 'hero/:id', component: HeroDetailComponent }
// #enddocregion hero-detail-route
];

View File

@ -9,8 +9,8 @@ import { HeroListComponent } from './hero-list.component';
// #docregion route-config
export const routes: RouterConfig = [
{ path: '/crisis-center', component: CrisisListComponent },
{ path: '/heroes', component: HeroListComponent }
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent }
];
export const APP_ROUTER_PROVIDERS = [

View File

@ -7,7 +7,7 @@ import { HeroesRoutes } from './heroes/heroes.routes';
export const routes = [
...HeroesRoutes,
{ path: '/crisis-center', component: CrisisListComponent }
{ path: 'crisis-center', component: CrisisListComponent }
];
export const APP_ROUTER_PROVIDERS = [

View File

@ -7,11 +7,11 @@ import { CrisisCenterComponent } from './crisis-center.component';
// #docregion routes
export const CrisisCenterRoutes: RouterConfig = [
{
path: '/crisis-center',
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{ path: '/', component: CrisisListComponent },
{ path: '/:id', component: CrisisDetailComponent }
{ path: ':id', component: CrisisDetailComponent },
{ path: '', component: CrisisListComponent }
]
}
];

View File

@ -6,17 +6,20 @@ import { CrisisCenterComponent } from './crisis-center.component';
// #docregion routes
export const CrisisCenterRoutes: RouterConfig = [
// #docregion redirect
{
path: '/crisis-center',
path: '',
redirectTo: '/crisis-center',
terminal: true
},
// #enddocregion redirect
{
path: 'crisis-center',
component: CrisisCenterComponent,
index: true,
children: [
{ path: '/:id', component: CrisisDetailComponent },
{ path: '/', component: CrisisListComponent,
index: true
}
{ path: ':id', component: CrisisDetailComponent },
{ path: '', component: CrisisListComponent }
]
}
];
// #enddocregion routes

View File

@ -10,25 +10,28 @@ import { CanDeactivateGuard } from '../interfaces';
export const CrisisCenterRoutes: RouterConfig = [
{
path: '/crisis-center',
path: '',
redirectTo: '/crisis-center',
terminal: true
},
{
path: 'crisis-center',
component: CrisisCenterComponent,
index: true,
children: [
// #docregion admin-route-no-guard
{
path: '/admin',
path: 'admin',
component: CrisisAdminComponent
},
// #enddocregion admin-route-no-guard
{
path: '/:id',
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
},
{
path: '/',
component: CrisisListComponent,
index: true
path: '',
component: CrisisListComponent
}
]
}
@ -40,7 +43,7 @@ export const CrisisCenterRoutes: RouterConfig = [
import { AuthGuard } from '../auth.guard';
{
path: '/admin',
path: 'admin',
component: CrisisAdminComponent,
canActivate: [AuthGuard]
}

View File

@ -11,25 +11,28 @@ import { AuthGuard } from '../auth.guard';
export const CrisisCenterRoutes: RouterConfig = [
{
path: '/crisis-center',
path: '',
redirectTo: '/crisis-center',
terminal: true
},
{
path: 'crisis-center',
component: CrisisCenterComponent,
index: true,
children: [
{
path: '/admin',
path: 'admin',
component: CrisisAdminComponent,
canActivate: [AuthGuard]
},
{
path: '/:id',
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
},
// #docregion default-route
{
path: '/',
component: CrisisListComponent,
index: true
path: '',
component: CrisisListComponent
}
// #enddocregion default-route
]
@ -42,7 +45,7 @@ export const CrisisCenterRoutes: RouterConfig = [
import { AuthGuard } from '../auth.guard';
{
path: '/admin',
path: 'admin',
component: CrisisAdminComponent,
canActivate: [AuthGuard]
}

View File

@ -10,25 +10,29 @@ import { AuthGuard } from '../auth.guard';
export const CrisisCenterRoutes: RouterConfig = [
{
path: '/crisis-center',
path: '',
redirectTo: '/crisis-center',
terminal: true
},
{
path: 'crisis-center',
component: CrisisCenterComponent,
index: true,
children: [
// #docregion admin-route
{
path: '/admin',
path: 'admin',
component: CrisisAdminComponent,
canActivate: [AuthGuard]
},
// #enddocregion admin-route
{
path: '/:id',
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
},
{ path: '/',
component: CrisisListComponent,
index: true
{
path: '',
component: CrisisListComponent
}
]
}

View File

@ -14,7 +14,7 @@ const CRISES = [
let crisesPromise = Promise.resolve(CRISES);
// #docregion
import {Injectable} from '@angular/core';
import { Injectable } from '@angular/core';
@Injectable()
export class CrisisService {

View File

@ -56,7 +56,7 @@ export class HeroDetailComponent implements OnInit, OnDestroy {
let heroId = this.hero ? this.hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
this.router.navigate(['/heroes'], { queryParams: { id: `${heroId}`, foo: 'foo' } });
this.router.navigate(['/heroes'], { queryParams: { id: heroId, foo: 'foo' } });
}
// #enddocregion gotoHeroes-navigate
}

View File

@ -4,9 +4,9 @@ import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
export const HeroesRoutes: RouterConfig = [
{ path: '/heroes', component: HeroListComponent },
{ path: 'heroes', component: HeroListComponent },
// #docregion hero-detail-route
{ path: '/hero/:id', component: HeroDetailComponent }
{ path: 'hero/:id', component: HeroDetailComponent }
// #enddocregion hero-detail-route
];
// #enddocregion

View File

@ -4,7 +4,6 @@ import { Router } from '@angular/router';
import { AuthService } from './auth.service';
@Component({
selector: 'login',
template: `
<h2>LOGIN</h2>
<p>{{message}}</p>
@ -25,7 +24,7 @@ export class LoginComponent {
}
login() {
this.message = "Trying to log in ...";
this.message = 'Trying to log in ...';
this.authService.login().subscribe(() => {
this.setMessage();

View File

@ -4,7 +4,7 @@ import { AuthService } from './auth.service';
import { LoginComponent } from './login.component';
export const LoginRoutes = [
{ path: '/login', component: LoginComponent }
{ path: 'login', component: LoginComponent }
];
export const AUTH_PROVIDERS = [AuthGuard, AuthService];

View File

@ -0,0 +1,25 @@
/// <reference path="../_protractor/e2e.d.ts" />
'use strict';
describe('Security E2E Tests', () => {
beforeAll(function() { browser.get(''); });
it('sanitizes innerHTML', () => {
let interpolated = element(By.className('e2e-inner-html-interpolated'));
expect(interpolated.getText())
.toContain('Template <script>alert("0wned")</script> <b>Syntax</b>');
let bound = element(By.className('e2e-inner-html-bound'));
expect(bound.getText()).toContain('Template alert("0wned") Syntax');
let bold = element(By.css('.e2e-inner-html-bound b'));
expect(bold.getText()).toContain('Syntax');
});
it('binds trusted URLs', () => {
let dangerousUrl = element(By.className('e2e-dangerous-url'));
expect(dangerousUrl.getAttribute('href')).toMatch(/^javascript:alert/);
});
it('binds trusted resource URLs', () => {
let iframe = element(By.className('e2e-iframe'));
expect(iframe.getAttribute('src')).toMatch(/^https:\/\/www.youtube.com\//);
});
});

View File

@ -0,0 +1,20 @@
// #docregion
import { Component } from '@angular/core';
import { BypassSecurityComponent } from './bypass-security.component';
import { InnerHtmlBindingComponent } from './inner-html-binding.component';
@Component({
selector: 'app-root',
template: `
<h1>Security</h1>
<inner-html-binding></inner-html-binding>
<bypass-security></bypass-security>
`,
directives: [
BypassSecurityComponent,
InnerHtmlBindingComponent,
],
})
export class AppComponent {
}

View File

@ -0,0 +1,15 @@
<!--#docregion -->
<h3>Bypass Security Component</h3>
<!--#docregion dangerous-url -->
<h4>A dangerous URL:</h4>
<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me.</a></p>
<!--#enddocregion dangerous-url -->
<!--#docregion iframe-videoid -->
<h4>Resource URL:</h4>
<p><label>Showing: <input (input)="updateVideoUrl($event.target.value)"></label></p>
<iframe class="e2e-iframe" width="640" height="390" [src]="videoUrl"></iframe>
<!--#enddocregion iframe-videoid -->
<!--#enddocregion -->

View File

@ -0,0 +1,33 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { DomSanitizationService, SafeResourceUrl, SafeUrl } from '@angular/platform-browser';
@Component({
selector: 'bypass-security',
templateUrl: 'app/bypass-security.component.html',
})
export class BypassSecurityComponent {
dangerousUrl: SafeUrl;
videoUrl: SafeResourceUrl;
// #docregion trust-url
constructor(private sanitizer: DomSanitizationService) {
// javascript: URLs are dangerous if attacker controlled. Angular sanitizes them in data
// binding, but we can explicitly tell Angular to trust this value:
this.dangerousUrl = sanitizer.bypassSecurityTrustUrl('javascript:alert("Hi there")');
// #enddocregion trust-url
this.updateVideoUrl('PUBnlbjZFAI');
}
// #docregion trust-video-url
updateVideoUrl(id: string) {
// Appending an ID to a YouTube URL is safe.
// Always make sure to construct SafeValue objects as close as possible to the input data, so
// that it's easier to check if the value is safe.
this.videoUrl =
this.sanitizer.bypassSecurityTrustResourceUrl('https://www.youtube.com/embed/' + id);
}
// #enddocregion trust-video-url
}
// #enddocregion

View File

@ -0,0 +1,6 @@
<!-- #docregion -->
<h3>Binding innerHTML</h3>
<p>Bound value:</p>
<p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>
<p>Result of binding to innerHTML:</p>
<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>

View File

@ -0,0 +1,14 @@
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'inner-html-binding',
templateUrl: 'inner-html-binding.component.html',
})
// #docregion inner-html-controller
export class InnerHtmlBindingComponent {
// E.g. a user/attacker controlled value from a URL.
htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';
}
// #enddocregion inner-html-controller

View File

@ -0,0 +1,8 @@
// #docregion
import { bootstrap } from '@angular/platform-browser-dynamic';
// #docregion import
import { AppComponent } from './app.component';
// #enddocregion import
bootstrap(AppComponent);

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Angular Content Security</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

View File

@ -0,0 +1,8 @@
{
"description": "Content Security",
"files": [
"!**/*.d.ts",
"!**/*.js"
],
"tags": ["security"]
}

View File

@ -1,11 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'toh-dashboard',
templateUrl: 'app/dashboard/dashboard.component.html'
})
export class DashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -1 +0,0 @@
export * from './dashboard.component';

View File

@ -1,11 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'toh-heroes',
templateUrl: 'app/heroes/heroes.component.html'
})
export class HeroesComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -1,2 +0,0 @@
export * from './shared';
export * from './heroes.component.ts';

View File

@ -1,8 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
constructor() { }
}

View File

@ -1 +0,0 @@
export * from './hero.service';

View File

@ -1,23 +0,0 @@
// #docregion
import { Component } from '@angular/core';
import { Routes, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router';
import { NavComponent } from './shared';
import { DashboardComponent } from './+dashboard';
import { HeroesComponent, HeroService } from './+heroes';
@Component({
selector: 'toh-app',
templateUrl: 'app/app.component.html',
styleUrls: ['app/app.component.css'],
directives: [ROUTER_DIRECTIVES, NavComponent],
providers: [
ROUTER_PROVIDERS,
HeroService
]
})
@Routes([
{ path: '/dashboard', component: DashboardComponent }, // , useAsDefault: true}, // coming soon
{ path: '/heroes/...', component: HeroesComponent },
])
export class AppComponent {}

View File

@ -1,4 +0,0 @@
export * from './+dashboard';
export * from './+heroes';
export * from './shared';
export * from './app.component';

View File

@ -1 +0,0 @@
export * from './nav';

View File

@ -1 +0,0 @@
export * from './nav.component';

View File

@ -1,12 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'toh-nav',
templateUrl: 'app/shared/nav/nav.component.html'
})
export class NavComponent implements OnInit {
constructor() { }
ngOnInit() { }
}

View File

@ -24,6 +24,7 @@
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',

View File

@ -5,8 +5,9 @@
*/
(function(global) {
var ngVer = '@2.0.0-rc.2'; // lock in the angular package version; do not let it float to current!
var routerVer = '@3.0.0-alpha.3'; // lock router version
var ngVer = '@2.0.0-rc.3'; // lock in the angular package version; do not let it float to current!
var routerVer = '@3.0.0-alpha.7'; // lock router version
var formsVer = '@0.1.1'; // lock forms version
//map tells the System loader where to look for things
var map = {
@ -14,6 +15,7 @@
'@angular': 'https://npmcdn.com/@angular', // sufficient if we didn't pin the version
'@angular/router': 'https://npmcdn.com/@angular/router' + routerVer,
'@angular/forms': 'https://npmcdn.com/@angular/forms' + formsVer,
'angular2-in-memory-web-api': 'https://npmcdn.com/angular2-in-memory-web-api', // get latest
'rxjs': 'https://npmcdn.com/rxjs@5.0.0-beta.6',
'ts': 'https://npmcdn.com/plugin-typescript@4.0.10/lib/plugin.js',
@ -57,6 +59,9 @@
// No umd for router yet
packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' };
// Forms not on rc yet
packages['@angular/forms'] = { main: 'index.js', defaultExtension: 'js' };
var config = {
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',

View File

@ -1,7 +1,7 @@
{
"globalDependencies": {
"core-js": "registry:dt/core-js#0.0.0+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
"node": "registry:dt/node#6.0.0+20160613154055"
"core-js": "registry:dt/core-js#0.0.0+20160602141332",
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
"node": "registry:dt/node#6.0.0+20160621231320"
}
}

View File

@ -25,6 +25,7 @@
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',

View File

@ -25,6 +25,7 @@
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',

View File

@ -10,12 +10,13 @@
},
"license": "MIT",
"dependencies": {
"@angular/common": "2.0.0-rc.2",
"@angular/compiler": "2.0.0-rc.2",
"@angular/core": "2.0.0-rc.2",
"@angular/http": "2.0.0-rc.2",
"@angular/platform-browser": "2.0.0-rc.2",
"@angular/platform-browser-dynamic": "2.0.0-rc.2",
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "0.1.1",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"@angular/router-deprecated": "2.0.0-rc.2",
"core-js": "^2.4.0",
"reflect-metadata": "0.1.2",

Some files were not shown because too many files have changed in this diff Show More