623 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			623 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <html lang="en"><head></head><body><form id="mainForm" method="post" action="http://plnkr.co/edit/?p=preview" target="_self"><input type="hidden" name="files[styles.css]" value="/* Master Styles */
 | |
| h1 {
 | |
|   color: #369;
 | |
|   font-family: Arial, Helvetica, sans-serif;
 | |
|   font-size: 250%;
 | |
| }
 | |
| h2, h3 {
 | |
|   color: #444;
 | |
|   font-family: Arial, Helvetica, sans-serif;
 | |
|   font-weight: lighter;
 | |
| }
 | |
| body {
 | |
|   margin: 2em;
 | |
| }
 | |
| body, input[text], button {
 | |
|   color: #888;
 | |
|   font-family: Cambria, Georgia;
 | |
| }
 | |
| a {
 | |
|   cursor: pointer;
 | |
|   cursor: hand;
 | |
| }
 | |
| button {
 | |
|   font-family: Arial;
 | |
|   background-color: #eee;
 | |
|   border: none;
 | |
|   padding: 5px 10px;
 | |
|   border-radius: 4px;
 | |
|   cursor: pointer;
 | |
|   cursor: hand;
 | |
| }
 | |
| button:hover {
 | |
|   background-color: #cfd8dc;
 | |
| }
 | |
| button:disabled {
 | |
|   background-color: #eee;
 | |
|   color: #aaa;
 | |
|   cursor: auto;
 | |
| }
 | |
| 
 | |
| /* Navigation link styles */
 | |
| nav a {
 | |
|   padding: 5px 10px;
 | |
|   text-decoration: none;
 | |
|   margin-right: 10px;
 | |
|   margin-top: 10px;
 | |
|   display: inline-block;
 | |
|   background-color: #eee;
 | |
|   border-radius: 4px;
 | |
| }
 | |
| nav a:visited, a:link {
 | |
|   color: #607D8B;
 | |
| }
 | |
| nav a:hover {
 | |
|   color: #039be5;
 | |
|   background-color: #CFD8DC;
 | |
| }
 | |
| nav a.active {
 | |
|   color: #039be5;
 | |
| }
 | |
| 
 | |
| /* items class */
 | |
| .items {
 | |
|   margin: 0 0 2em 0;
 | |
|   list-style-type: none;
 | |
|   padding: 0;
 | |
|   width: 24em;
 | |
| }
 | |
| .items li {
 | |
|   cursor: pointer;
 | |
|   position: relative;
 | |
|   left: 0;
 | |
|   background-color: #EEE;
 | |
|   margin: .5em;
 | |
|   padding: .3em 0;
 | |
|   height: 1.6em;
 | |
|   border-radius: 4px;
 | |
| }
 | |
| .items li:hover {
 | |
|   color: #607D8B;
 | |
|   background-color: #DDD;
 | |
|   left: .1em;
 | |
| }
 | |
| .items li.selected {
 | |
|   background-color: #CFD8DC;
 | |
|   color: white;
 | |
| }
 | |
| .items li.selected:hover {
 | |
|   background-color: #BBD8DC;
 | |
| }
 | |
| .items .text {
 | |
|   position: relative;
 | |
|   top: -3px;
 | |
| }
 | |
| .items .badge {
 | |
|   display: inline-block;
 | |
|   font-size: small;
 | |
|   color: white;
 | |
|   padding: 0.8em 0.7em 0 0.7em;
 | |
|   background-color: #607D8B;
 | |
|   line-height: 1em;
 | |
|   position: relative;
 | |
|   left: -1px;
 | |
|   top: -4px;
 | |
|   height: 1.8em;
 | |
|   margin-right: .8em;
 | |
|   border-radius: 4px 0 0 4px;
 | |
| }
 | |
| /* everywhere else */
 | |
| * {
 | |
|   font-family: Arial, Helvetica, sans-serif;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| Copyright 2016 Google Inc. All Rights Reserved.
 | |
| Use of this source code is governed by an MIT-style license that
 | |
| can be found in the LICENSE file at http://angular.io/license
 | |
| */"><input type="hidden" name="files[app/bag/bag.ts]" value="/* tslint:disable:forin */
 | |
| import { Component, ContentChildren, Directive, EventEmitter,
 | |
|          Injectable, Input, Output, Optional,
 | |
|          HostBinding, HostListener,
 | |
|          OnInit, OnChanges, OnDestroy,
 | |
|          Pipe, PipeTransform,
 | |
|          SimpleChange } from '@angular/core';
 | |
| 
 | |
| import { Observable } from 'rxjs/Observable';
 | |
| import 'rxjs/add/observable/of';
 | |
| import 'rxjs/add/operator/delay';
 | |
| 
 | |
| ////////// The App: Services and Components for the tests. //////////////
 | |
| 
 | |
| export class Hero {
 | |
|   name: string;
 | |
| }
 | |
| 
 | |
| ////////// Services ///////////////
 | |
| @Injectable()
 | |
| export class FancyService {
 | |
|   protected value: string = 'real value';
 | |
| 
 | |
|   getValue() { return this.value; }
 | |
|   setValue(value: string) { this.value = value; }
 | |
| 
 | |
|   getAsyncValue() { return Promise.resolve('async value'); }
 | |
| 
 | |
|   getObservableValue() { return Observable.of('observable value'); }
 | |
| 
 | |
|   getTimeoutValue() {
 | |
|     return new Promise((resolve) => {
 | |
|       setTimeout(() => { resolve('timeout value'); }, 10);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   getObservableDelayValue() {
 | |
|     return Observable.of('observable delay value').delay(10);
 | |
|   }
 | |
| }
 | |
| 
 | |
| @Injectable()
 | |
| export class DependentService {
 | |
|   constructor(private dependentService: FancyService) { }
 | |
|   getValue() { return this.dependentService.getValue(); }
 | |
| }
 | |
| 
 | |
| /////////// Pipe ////////////////
 | |
| /*
 | |
|  * Reverse the input string.
 | |
| */
 | |
| @Pipe({ name: 'reverse' })
 | |
| export class ReversePipe implements PipeTransform {
 | |
|   transform(s: string) {
 | |
|     let r = '';
 | |
|     for (let i = s.length; i; )  { r += s[--i]; };
 | |
|     return r;
 | |
|   }
 | |
| }
 | |
| 
 | |
| //////////// Components /////////////
 | |
| @Component({
 | |
|   selector: 'bank-account',
 | |
|   template: `
 | |
|    Bank Name: {{bank}}
 | |
|    Account Id: {{id}}
 | |
|  `
 | |
| })
 | |
| export class BankAccountComponent {
 | |
|   @Input() bank: string;
 | |
|   @Input('account') id: string;
 | |
| 
 | |
|   // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?
 | |
|   // constructor(private renderer: Renderer, private el: ElementRef ) {
 | |
|   //   renderer.setElementProperty(el.nativeElement, 'customProperty', true);
 | |
|   // }
 | |
| }
 | |
| 
 | |
| /** A component with attributes, styles, classes, and property setting */
 | |
| @Component({
 | |
|   selector: 'bank-account-parent',
 | |
|   template: `
 | |
|    <bank-account
 | |
|       bank="RBC"
 | |
|       account="4747"
 | |
|       [style.width.px]="width"
 | |
|       [style.color]="color"
 | |
|       [class.closed]="isClosed"
 | |
|       [class.open]="!isClosed">
 | |
|    </bank-account>
 | |
|  `
 | |
| })
 | |
| export class BankAccountParentComponent {
 | |
|   width = 200;
 | |
|   color = 'red';
 | |
|   isClosed = true;
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'button-comp',
 | |
|   template: `
 | |
|     <button (click)="clicked()">Click me!</button>
 | |
|     <span>{{message}}</span>`
 | |
| })
 | |
| export class ButtonComponent {
 | |
|   isOn = false;
 | |
|   clicked() { this.isOn = !this.isOn; }
 | |
|   get message() { return `The light is ${this.isOn ? 'On' : 'Off'}`; }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'child-1',
 | |
|   template: `<span>Child-1({{text}})</span>`
 | |
| })
 | |
| export class Child1Component {
 | |
|   @Input() text = 'Original';
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'child-2',
 | |
|   template: '<div>Child-2({{text}})</div>'
 | |
| })
 | |
| export class Child2Component {
 | |
|   @Input() text: string;
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'child-3',
 | |
|   template: '<div>Child-3({{text}})</div>'
 | |
| })
 | |
| export class Child3Component {
 | |
|   @Input() text: string;
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'input-comp',
 | |
|   template: `<input [(ngModel)]="name">`
 | |
| })
 | |
| export class InputComponent {
 | |
|   name = 'John';
 | |
| }
 | |
| 
 | |
| /* Prefer this metadata syntax */
 | |
| // @Directive({
 | |
| //   selector: 'input[value]',
 | |
| //   host: {
 | |
| //     '[value]': 'value',
 | |
| //     '(input)': 'valueChange.emit($event.target.value)'
 | |
| //   },
 | |
| //   inputs:  ['value'],
 | |
| //   outputs: ['valueChange']
 | |
| // })
 | |
| // export class InputValueBinderDirective {
 | |
| //   value: any;
 | |
| //   valueChange: EventEmitter<any> = new EventEmitter();
 | |
| // }
 | |
| 
 | |
| // As the style-guide recommends
 | |
| @Directive({ selector: 'input[value]' })
 | |
| export class InputValueBinderDirective {
 | |
|   @HostBinding()
 | |
|   @Input()
 | |
|   value: any;
 | |
| 
 | |
|   @Output()
 | |
|   valueChange: EventEmitter<any> = new EventEmitter();
 | |
| 
 | |
|   @HostListener('input', ['$event.target.value'])
 | |
|   onInput(value: any) { this.valueChange.emit(value); }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'input-value-comp',
 | |
|   template: `
 | |
|     Name: <input [(value)]="name"> {{name}}
 | |
|   `
 | |
| })
 | |
| export class InputValueBinderComponent {
 | |
|   name = 'Sally'; // initial value
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'parent-comp',
 | |
|   template: `Parent(<child-1></child-1>)`
 | |
| })
 | |
| export class ParentComponent { }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'io-comp',
 | |
|   template: `<div class="hero" (click)="click()">Original {{hero.name}}</div>`
 | |
| })
 | |
| export class IoComponent {
 | |
|   @Input() hero: Hero;
 | |
|   @Output() selected = new EventEmitter<Hero>();
 | |
|   click() { this.selected.emit(this.hero); }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'io-parent-comp',
 | |
|   template: `
 | |
|   <p *ngIf="!selectedHero"><i>Click to select a hero</i></p>
 | |
|   <p *ngIf="selectedHero">The selected hero is {{selectedHero.name}}</p>
 | |
|   <io-comp
 | |
|     *ngFor="let hero of heroes"
 | |
|     [hero]=hero
 | |
|     (selected)="onSelect($event)">
 | |
|   </io-comp>
 | |
|   `
 | |
| })
 | |
| export class IoParentComponent {
 | |
|   heroes: Hero[] = [ {name: 'Bob'}, {name: 'Carol'}, {name: 'Ted'}, {name: 'Alice'} ];
 | |
|   selectedHero: Hero;
 | |
|   onSelect(hero: Hero) { this.selectedHero = hero; }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'my-if-comp',
 | |
|   template: `MyIf(<span *ngIf="showMore">More</span>)`
 | |
| })
 | |
| export class MyIfComponent {
 | |
|   showMore = false;
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'my-service-comp',
 | |
|   template: `injected value: {{fancyService.value}}`,
 | |
|   providers: [FancyService]
 | |
| })
 | |
| export class TestProvidersComponent {
 | |
|   constructor(private fancyService: FancyService) {}
 | |
| }
 | |
| 
 | |
| 
 | |
| @Component({
 | |
|   selector: 'my-service-comp',
 | |
|   template: `injected value: {{fancyService.value}}`,
 | |
|   viewProviders: [FancyService]
 | |
| })
 | |
| export class TestViewProvidersComponent {
 | |
|   constructor(private fancyService: FancyService) {}
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   moduleId: module.id,
 | |
|   selector: 'external-template-comp',
 | |
|   templateUrl: './bag-external-template.html'
 | |
| })
 | |
| export class ExternalTemplateComponent implements OnInit {
 | |
|   serviceValue: string;
 | |
| 
 | |
|   constructor(@Optional() private service: FancyService) {  }
 | |
| 
 | |
|   ngOnInit() {
 | |
|     if (this.service) { this.serviceValue = this.service.getValue(); }
 | |
|   }
 | |
| }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'comp-w-ext-comp',
 | |
|   template: `
 | |
|   <h3>comp-w-ext-comp</h3>
 | |
|   <external-template-comp></external-template-comp>
 | |
|   `
 | |
| })
 | |
| export class InnerCompWithExternalTemplateComponent { }
 | |
| 
 | |
| @Component({
 | |
|   moduleId: module.id,
 | |
|   selector: 'bad-template-comp',
 | |
|   templateUrl: './non-existant.html'
 | |
| })
 | |
| export class BadTemplateUrlComponent { }
 | |
| 
 | |
| 
 | |
| 
 | |
| @Component({selector: 'needs-content', template: '<ng-content></ng-content>'})
 | |
| export class NeedsContentComponent {
 | |
|   // children with #content local variable
 | |
|   @ContentChildren('content') children: any;
 | |
| }
 | |
| 
 | |
| ///////// MyIfChildComp ////////
 | |
| @Component({
 | |
|   selector: 'my-if-child-1',
 | |
| 
 | |
|   template: `
 | |
|     <h4>MyIfChildComp</h4>
 | |
|     <div>
 | |
|       <label>Child value: <input [(ngModel)]="childValue"> </label>
 | |
|     </div>
 | |
|     <p><i>Change log:</i></p>
 | |
|     <div *ngFor="let log of changeLog; let i=index">{{i + 1}} - {{log}}</div>`
 | |
| })
 | |
| export class MyIfChildComponent implements OnInit, OnChanges, OnDestroy {
 | |
|   @Input() value = '';
 | |
|   @Output() valueChange = new EventEmitter<string>();
 | |
| 
 | |
|   get childValue() { return this.value; }
 | |
|   set childValue(v: string) {
 | |
|     if (this.value === v) { return; }
 | |
|     this.value = v;
 | |
|     this.valueChange.emit(v);
 | |
|   }
 | |
| 
 | |
|   changeLog: string[] = [];
 | |
| 
 | |
|   ngOnInitCalled = false;
 | |
|   ngOnChangesCounter = 0;
 | |
|   ngOnDestroyCalled = false;
 | |
| 
 | |
|   ngOnInit()    {
 | |
|     this.ngOnInitCalled = true;
 | |
|     this.changeLog.push('ngOnInit called');
 | |
|   }
 | |
| 
 | |
|   ngOnDestroy() {
 | |
|     this.ngOnDestroyCalled = true;
 | |
|     this.changeLog.push('ngOnDestroy called');
 | |
|   }
 | |
| 
 | |
|   ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
 | |
|     for (let propName in changes) {
 | |
|       this.ngOnChangesCounter += 1;
 | |
|       let prop = changes[propName];
 | |
|       let cur  = JSON.stringify(prop.currentValue);
 | |
|       let prev = JSON.stringify(prop.previousValue);
 | |
|       this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| ///////// MyIfParentComp ////////
 | |
| 
 | |
| @Component({
 | |
|   selector: 'my-if-parent-comp',
 | |
|   template: `
 | |
|     <h3>MyIfParentComp</h3>
 | |
|     <label>Parent value:
 | |
|       <input [(ngModel)]="parentValue">
 | |
|     </label>
 | |
|     <button (click)="clicked()">{{toggleLabel}} Child</button><br>
 | |
|     <div *ngIf="showChild"
 | |
|          style="margin: 4px; padding: 4px; background-color: aliceblue;">
 | |
|       <my-if-child-1  [(value)]="parentValue"></my-if-child-1>
 | |
|     </div>
 | |
|   `
 | |
| })
 | |
| export class MyIfParentComponent implements OnInit {
 | |
|   ngOnInitCalled = false;
 | |
|   parentValue = 'Hello, World';
 | |
|   showChild = false;
 | |
|   toggleLabel = 'Unknown';
 | |
| 
 | |
|   ngOnInit() {
 | |
|     this.ngOnInitCalled = true;
 | |
|     this.clicked();
 | |
|   }
 | |
| 
 | |
|   clicked() {
 | |
|     this.showChild = !this.showChild;
 | |
|     this.toggleLabel = this.showChild ? 'Close' : 'Show';
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| @Component({
 | |
|   selector: 'reverse-pipe-comp',
 | |
|   template: `
 | |
|     <input [(ngModel)]="text">
 | |
|     <span>{{text | reverse}}</span>
 | |
|   `
 | |
| })
 | |
| export class ReversePipeComponent {
 | |
|   text = 'my dog has fleas.';
 | |
| }
 | |
| 
 | |
| @Component({template: '<div>Replace Me</div>'})
 | |
| export class ShellComponent { }
 | |
| 
 | |
| @Component({
 | |
|   selector: 'bag-comp',
 | |
|   template: `
 | |
|     <h1>Specs Bag</h1>
 | |
|     <my-if-parent-comp></my-if-parent-comp>
 | |
|     <hr>
 | |
|     <h3>Input/Output Component</h3>
 | |
|     <io-parent-comp></io-parent-comp>
 | |
|     <hr>
 | |
|     <h3>External Template Component</h3>
 | |
|     <external-template-comp></external-template-comp>
 | |
|     <hr>
 | |
|     <h3>Component With External Template Component</h3>
 | |
|     <comp-w-ext-comp></comp-w-ext-comp>
 | |
|     <hr>
 | |
|     <h3>Reverse Pipe</h3>
 | |
|     <reverse-pipe-comp></reverse-pipe-comp>
 | |
|     <hr>
 | |
|     <h3>InputValueBinder Directive</h3>
 | |
|     <input-value-comp></input-value-comp>
 | |
|     <hr>
 | |
|     <h3>Button Component</h3>
 | |
|     <button-comp></button-comp>
 | |
|     <hr>
 | |
|     <h3>Needs Content</h3>
 | |
|     <needs-content #nc>
 | |
|       <child-1 #content text="My"></child-1>
 | |
|       <child-2 #content text="dog"></child-2>
 | |
|       <child-2 text="has"></child-2>
 | |
|       <child-3 #content text="fleas"></child-3>
 | |
|       <div #content>!</div>
 | |
|     </needs-content>
 | |
|   `
 | |
| })
 | |
| export class BagComponent { }
 | |
| //////// Aggregations ////////////
 | |
| 
 | |
| export const bagDeclarations = [
 | |
|   BagComponent,
 | |
|   BankAccountComponent, BankAccountParentComponent,
 | |
|   ButtonComponent,
 | |
|   Child1Component, Child2Component, Child3Component,
 | |
|   ExternalTemplateComponent, InnerCompWithExternalTemplateComponent,
 | |
|   InputComponent,
 | |
|   InputValueBinderDirective, InputValueBinderComponent,
 | |
|   IoComponent, IoParentComponent,
 | |
|   MyIfComponent, MyIfChildComponent, MyIfParentComponent,
 | |
|   NeedsContentComponent, ParentComponent,
 | |
|   TestProvidersComponent, TestViewProvidersComponent,
 | |
|   ReversePipe, ReversePipeComponent, ShellComponent
 | |
| ];
 | |
| 
 | |
| export const bagProviders = [DependentService, FancyService];
 | |
| 
 | |
| ////////////////////
 | |
| ////////////
 | |
| import { NgModule }      from '@angular/core';
 | |
| import { BrowserModule } from '@angular/platform-browser';
 | |
| import { FormsModule }   from '@angular/forms';
 | |
| 
 | |
| @NgModule({
 | |
|   imports: [BrowserModule, FormsModule],
 | |
|   declarations: bagDeclarations,
 | |
|   providers:    bagProviders,
 | |
|   entryComponents: [BagComponent],
 | |
|   bootstrap:       [BagComponent]
 | |
| })
 | |
| export class BagModule { }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
| Copyright 2016 Google Inc. All Rights Reserved.
 | |
| Use of this source code is governed by an MIT-style license that
 | |
| can be found in the LICENSE file at http://angular.io/license
 | |
| */"><input type="hidden" name="files[app/bag/bag-external-template.html]" value="<span>from external template</span>
 | |
| 
 | |
| 
 | |
| <!-- 
 | |
| Copyright 2016 Google Inc. All Rights Reserved.
 | |
| Use of this source code is governed by an MIT-style license that
 | |
| can be found in the LICENSE file at http://angular.io/license
 | |
| -->"><input type="hidden" name="files[app/bag/bag-main.ts]" value="// main app entry point
 | |
| import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
 | |
| import { BagModule } from './bag';
 | |
| 
 | |
| platformBrowserDynamic().bootstrapModule(BagModule);
 | |
| 
 | |
| 
 | |
| /*
 | |
| Copyright 2016 Google Inc. All Rights Reserved.
 | |
| Use of this source code is governed by an MIT-style license that
 | |
| can be found in the LICENSE file at http://angular.io/license
 | |
| */"><input type="hidden" name="files[index.html]" value="<!-- Run the bag source as an app -->
 | |
| <!DOCTYPE html>
 | |
| <html>
 | |
|   <head>
 | |
|     <script>document.write('<base href="' + document.location + '" />');</script>
 | |
|     <title>Specs Bag</title>
 | |
|     <meta charset="UTF-8">
 | |
|     <meta name="viewport" content="width=device-width, initial-scale=1">
 | |
|     <link rel="stylesheet" href="styles.css">
 | |
| 
 | |
|     <!-- Polyfills -->
 | |
|     <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
 | |
| 
 | |
|     <script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
 | |
|     <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
 | |
| 
 | |
|     <script src="https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js"></script>
 | |
|     <script>
 | |
|       System.import('app/bag/bag-main').catch(function(err){ console.error(err); });
 | |
|     </script>
 | |
|   </head>
 | |
| 
 | |
|   <body>
 | |
|     <bag-comp>Loading ...</bag-comp>
 | |
|   </body>
 | |
| </html>
 | |
| 
 | |
| 
 | |
| <!-- 
 | |
| Copyright 2016 Google Inc. All Rights Reserved.
 | |
| Use of this source code is governed by an MIT-style license that
 | |
| can be found in the LICENSE file at http://angular.io/license
 | |
| -->"><input type="hidden" name="tags[0]" value="angular"><input type="hidden" name="tags[1]" value="example"><input type="hidden" name="tags[2]" value="testing"><input type="hidden" name="private" value="true"><input type="hidden" name="description" value="Angular Example - Running the bag"></form><script>document.getElementById("mainForm").submit();</script></body></html> |