typescript mocks.ts

mocks.ts
/// <reference path="./index.d.ts"/>

import { MockUtils } from './MockUtils';
import 'ngMockE2E';

let moduleName = 'stMocks';

angular.module(moduleName, ['ngMockE2E'])
	.service('$mockUtils', MockUtils)
	.constant('mockDelay', 0)
	.config(['$provide', 'mockDelay', function ($provide: ng.auto.IProvideService, mockDelay: number) {
		if ( !mockDelay ) {
			return;
		}
		$provide.decorator('$httpBackend', ($delegate: any) => {
			let proxy: any = (method: string,
									url: string,
									data: any,
									callback: Function,
									headers: any,
									timeout: number|ng.IPromise<void>,
									withCredentials: boolean) => {

				let interceptor = function () {
					let self = this;
					let _arguments = arguments;

					// для шаблонов задержку не надо
					if ( _.includes(url, 'html') ) {
						callback.apply(self, _arguments);
					} else {
						setTimeout(() => {
							// return result to the client AFTER delay
							callback.apply(self, _arguments);
						}, mockDelay);
					}
				};

				return $delegate.call(this, method, url, data, interceptor, headers, timeout, withCredentials);
			};
			for (let key in $delegate) {
				if ( $delegate.hasOwnProperty(key) ) {
					proxy[key] = $delegate[key];
				}
			}
			return proxy;
		});
	}])
	.run(['$httpBackend', ($httpBackend: ng.IHttpBackendService) => {

		// в этом разделе хакается $httpBackend, добавляется логирование запросов и ответов
		// в код можно не лазить, править не нужно, just works
		function logResult(result: any, args: any, status: number) {
			let $log: any = console;
			let method = args[0];
			let url = args[1];
			let data = args[2];
			let headers = args[3];
			$log.groupCollapsed('[' + method + '] ' + decodeURI(url));

			$log.log('input:', data !== undefined ? JSON.parse(data) : undefined);

			if ( angular.isArray(result) ) {
				$log.log('output:');
				if ( _.isFunction($log.table) ) {
					$log.table(result);
				}
				$log.dir(result);
			} else {
				$log.log('output (' + status + '):', result);
			}

			$log.log('headers:', headers);

			$log.groupEnd();
		}

		let createResponse = (status: number|Function, data: any, headers: any, statusText: string) => {
			let result: Function;
			if ( angular.isFunction(status) ) {
				result = <Function>status;
			} else {
				result = () => angular.isNumber(status) ?
					[status, data, headers, statusText] :
					[200, status, data];
			}
			return function () {
				let response = result.apply(undefined, arguments);
				logResult(response[1], arguments, response[0]);
				return response;
			};
		};

		// monkey-patching для поддержки логирования в консоль результатов запросов
		_.forEach(['whenGET', 'whenPOST', 'whenPUT', 'whenDELETE'], (method: string) => {
			(<any>$httpBackend)[method] = _.wrap((<any>$httpBackend)[method],
				(f: Function, method_: string, url: string, inputData: any, headers: () => any) => {
					let result = f(method_, url, inputData, headers);

					result.respond = _.wrap(result.respond,
						(respond: Function,
						 status: number,
						 data: any,
						 headers_: () => any,
						 statusText: string) => {
							let generator = createResponse(status, data, headers_, statusText);
							respond(generator);
						});
					return result;
				});
		});
	}])
	.run(['$httpBackend', '$mockUtils', ($httpBackend: ng.IHttpBackendService, $mockUtils: mocks.IMockUtils) => {
		// шаблоны всегда грузим с сервера
		$httpBackend.whenGET(/\.html$/).passThrough();
	}]);

export = moduleName;

typescript 电报打字

电报打字

TL.d.ts
// A typings definition of some telegram data structure
// author: Richard He<richard9372@gmail.com>
// https://gist.github.com/richard1122/1eb54cd4e422aeb707718ab307decd34
// see more detail: https://core.telegram.org/bots/api

declare namespace TL {
    export interface IResponse<T> {
        ok:boolean
        result:T
    }
    
    export interface IMessage {
        message_id:number
        from:IUser
        date:number
        chat:IChat
        forward_from?:IUser
        forward_from_chat?:IChat
        forward_date?:number
        reply_to_message?:IMessage
        edit_date?:number
        text?:string
        entities?:Array<IMessageEntity>
        audio?:IAudio
        document?:IDocument
        photo?:Array<IPhotoSize>
        sticker?:ISticker
        video?:IVideo
        voice?:IVoice
        caption?:string
        contack?:IContact
        location?:ILocation
        venue?:IVenue
        new_chat_member?:IUser
        left_chat_member?:IUser
        new_chat_title?:string
        new_chat_photo?:Array<IPhotoSize>
        delete_chat_photo?:boolean
        group_chat_created?:boolean
        supergroup_chat_created?:boolean
        channel_chat_created?:boolean
        migrate_to_chat_id?:number
        migrate_from_chat_id?:number
        pinned_message:IMessage
    }
    
    export interface IMessageUpdate {
        update_id:number
        message:IMessage
    }
    
    export interface IUser {
        id:number
        first_name:string
        last_name?:string
        username?:string
    }
    
    export type ChatType = 'private' | 'group' | 'supergroup' | 'channel'
    
    export interface IChat {
        id:number
        type:ChatType
        title?:string
        username?:string
        first_name?:string
        last_name?:string
    }
    
    export type MessageEntityType = 'mention' | 'hashtag' | 'bot_command' | 'url' | 'email' | 'bold' | 'italic' | 'code' | 'pre' | 'text_link' | 'text_mention'
    
    export interface IMessageEntity {
        type:MessageEntityType
        offset:number
        length:number
        url?:string
        user?:IUser
    }
    
    export interface IFile {
        file_id:string
        file_size?:number
        file_path?:string
    }
    
    export interface IPhotoSize extends IFile {
        width:number
        height:number
    }
    
    export interface IAudio extends IFile {
        duration:number
        performer?:string
        title?:string
        mime_type?:string
    }
    
    export interface IDocument extends IFile {
        thumb?:IPhotoSize
        file_name?:string
        mime_type?:string
    }
    
    export interface ISticker extends IFile {
        width:number
        height:number
        thumb?:IPhotoSize
        emoji?:string
    }
    
    export interface IVideo extends IFile {
        width:number
        height:number
        duration:number
        thumb?:IPhotoSize
        mime_type?:string
    }
    
    export interface IVoice extends IFile {
        duration:number
        mime_type?:string
    }
    
    export interface IContact {
        phone_number:string
        first_name:string
        last_name?:string
        user_id?:number
    }
    
    export interface ILocation {
        longitude:number
        latitude:number
    }
    
    export interface IVenue {
        location:ILocation
        title:string
        address:string
        foursquare_id?:string
    }
    
    export interface IUserProfilePhotos {
        total_count:number
        photos:Array<IPhotoSize>
    }
}

typescript HTTP和路由 - 基本路由

HTTP和路由 - 基本路由

t3.component.ts
import { Component, Inject, OnInit } from '@angular/core';
import { Path1Component } from './path1/path1.component'
import { Path2Component } from './path2/path2.component'
import { ROUTER_DIRECTIVES, Routes, Router } from '@angular/router';


@Component({
  moduleId: module.id,
  selector: 't3-app',
  templateUrl: 't3.component.html',
  styleUrls: ['t3.component.css'],
  directives: [ROUTER_DIRECTIVES]
})

@Routes([
  {path:'/path1', component: Path1Component},
  {path:'/path2', component: Path2Component}
])

export class T3AppComponent implements OnInit{
  ngOnInit(){
    //this.router.navigate(['/path1']);
  }
}
t3.component.html
<nav>
	<a [routerLink]="['/path1']">Page 1</a>
	<a [routerLink]="['/path2']">Page 2</a>
</nav>
<router-outlet></router-outlet>
main.ts
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode, provide } from '@angular/core';
import { T3AppComponent, environment } from './app/';
import { ROUTER_PROVIDERS } from '@angular/router';

if (environment.production) {
  enableProdMode();
}

bootstrap(T3AppComponent,[
	ROUTER_PROVIDERS
]);

typescript HTTP和路由 - 获取HTTP

HTTP和路由 - 获取HTTP

t4.component.ts
import { Component, OnInit } from '@angular/core';
import { IProduct } from './product';
import { MyServiceService } from './my-service.service';
import { HTTP_PROVIDERS } from '@angular/http';
import 'rxjs/Rx';

@Component({
  moduleId: module.id,
  selector: 't4-app',
  templateUrl: 't4.component.html',
  styleUrls: ['t4.component.css'],
  providers: [MyServiceService,
  			  HTTP_PROVIDERS]
})
export class T4AppComponent implements OnInit {
  title : IProduct[];
  errorMessage: string;

  constructor(private myServiceService : MyServiceService){

  }

  ngOnInit(): void {
  	this.myServiceService.getProduct()
  		.subscribe(
  			title => this.title = title,
  			error => this.errorMessage = <any>error
  		);
  }
}
t4.component.html
<h1>
  {{title | json}}
</h1>
products.json
[
  {
      "productId": 1,
      "productName": "Nisar"
  },
  {
      "productId": 2,
      "productName": "Lado"
  }
]
product.ts
export interface IProduct{
	productId: number;
	productName: string;
}
my-service.service.ts
import { Injectable } from '@angular/core';
import { IProduct } from './product';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class MyServiceService {
  private productUrl = '/app/products.json';
  constructor(private http: Http) {}

  getProduct(): Observable<IProduct[]> {
        return this.http.get(this.productUrl)
            .map((response: Response) => <IProduct[]>response.json())
            .do(data => console.log("All: " +  JSON.stringify(data)))
            .catch(this.handleError);
    }

   private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
   }
}

typescript DI - 不透明的令牌

DI - 不透明的令牌

t2.component.ts
import { Component, Inject } from '@angular/core';
import { Control, Validators, FormBuilder } from '@angular/common';
import { MyServiceService } from './my-service.service'
import { LOOKUP_LISTS } from './providers';

@Component({
  moduleId: module.id,
  selector: 't3-app',
  templateUrl: 't3.component.html',
  styleUrls: ['t3.component.css']
})
export class T3AppComponent {
  form;
  mediaItems:any;

  constructor(private formBuilder: FormBuilder,
  	private myServiceService: MyServiceService,
  	@Inject(LOOKUP_LISTS) public lookupLists
  ) {}

  ngOnInit(){
  	this.mediaItems = this.myServiceService.get();

  	this.form = this.formBuilder.group({
  	  'medium': new Control('Movies'),
  	  'name': new Control('',Validators.compose([
  	  	Validators.required,
  	  	Validators.pattern('[\\w\\-\\s\\/]+'),
  	  ])),
  	  'year': new Control('',this.yearValidation)
  	});
  }

  yearValidation(control){
  	if(control.value.trim().length === 0) return null;
  	var year = parseInt(control.value);
  	var minYear = 2000;
  	var maxYear = 2010;
  	if(year >= minYear && year <= maxYear) return null;
  	return {'year': {'min':minYear, 'max':maxYear}}
  }

  title:any;
  
  onSubmit(data){
  	this.title = data;
  }
}
t2.component.html
<h1>
  {{title | json}} 
</h1>
<form (ngSubmit)="onSubmit(form.value)" [ngFormModel]="form">
	<ul>
		<li>
			<label for="medium">Medium</label>
			<select name="medium" id="medium" ngControl="medium">
				<option *ngFor="let medium of lookupLists.mediums" value="{{medium}}">{{medium}}</option>
			</select>
		</li>
		<li>
			<label for="name">Name</label>
			<input type="text" name="name" id="name" ngControl="name"
			  ngControl="name" 
			  #name="ngForm"
			/>
			<div *ngIf="name.errors?.pattern" class="error">name is invalid</div>
		</li>
		<li>
			<label for="year">Year</label>
			<input type="year" name="year" id="year" ngControl="year"
			  ngControl="year"
			  #year="ngForm"
			/>
			<div *ngIf="year.errors?.year" class="error">must be between {{year.errors?.year.min}} and {{year.errors?.year.max}}</div>
		</li>
	</ul>
	<button type="submit" [disabled]="!form.valid ">Save</button>
	<p>
		{{mediaItems | json}}
	</p>
</form>
provider.ts
import {OpaqueToken} from '@angular/core';

export var LOOKUP_LISTS = new OpaqueToken('LookupLists');

export var lookupLists = {
  mediums: ['Movies', 'Series', 'Sample2'] 
}
my-service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class MyServiceService {

  constructor() {}
  get(){
  	return this.mediaItems;
  }
  add(mediaItem){
  	this.mediaItems.push(mediaItem);
  }
  delete(mediaItem){
  	var index = this.mediaItems.indexOf(mediaItem);
  	if(index >= 0){
  		this.mediaItems.splice(index,1);
  	}
  }
  mediaItems = [
        {
            id: 1,
            name: "Firebug",
            medium: "Series",
            category: "Science Fiction",
            year: 2010,
            watchedOn: 1294166565384,
            isFavorite: false
        },
        {
            id: 2,
            name: "The Small Tall",
            medium: "Movies",
            category: "Comedy",
            year: 2015,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 3,
            name: "The Redemption",
            medium: "Movies",
            category: "Action",
            year: 2016,
            watchedOn: null,
            isFavorite: false
        }, {
            id: 4,
            name: "Hoopers",
            medium: "Series",
            category: "Drama",
            year: null,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 5,
            name: "Happy Joe: Cheery Road",
            medium: "Movies",
            category: "Action",
            year: 2015,
            watchedOn: 1457166565384,
            isFavorite: false
        }
    ];
}
main.ts
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode, provide } from '@angular/core';
import { T3AppComponent, environment } from './app/';
import { MyServiceService } from './app/my-service.service';
import { LOOKUP_LISTS, lookupLists } from './app/providers';

if (environment.production) {
  enableProdMode();
}

bootstrap(T3AppComponent,[
	MyServiceService,
	provide(LOOKUP_LISTS,{useValue: lookupLists})
]);

typescript DI - Bootstrap和Inject装饰器上的提供者注册

DI - Bootstrap和Inject装饰器上的提供者注册

t2.component.ts
import { Component, Inject } from '@angular/core';
import { Control, Validators, FormBuilder } from '@angular/common';
import { MyServiceService } from './my-service.service'

@Component({
  moduleId: module.id,
  selector: 't3-app',
  templateUrl: 't3.component.html',
  styleUrls: ['t3.component.css']
})
export class T3AppComponent {
  form;
  mediaItems:any;

  constructor(private formBuilder: FormBuilder,
  	private myServiceService: MyServiceService,
  	@Inject('LOOKUP_LISTS') public lookupLists
  ) {}

  ngOnInit(){
  	this.mediaItems = this.myServiceService.get();

  	this.form = this.formBuilder.group({
  	  'medium': new Control('Movies'),
  	  'name': new Control('',Validators.compose([
  	  	Validators.required,
  	  	Validators.pattern('[\\w\\-\\s\\/]+'),
  	  ])),
  	  'year': new Control('',this.yearValidation)
  	});
  }

  yearValidation(control){
  	if(control.value.trim().length === 0) return null;
  	var year = parseInt(control.value);
  	var minYear = 2000;
  	var maxYear = 2010;
  	if(year >= minYear && year <= maxYear) return null;
  	return {'year': {'min':minYear, 'max':maxYear}}
  }

  title:any;
  
  onSubmit(data){
  	this.title = data;
  }
}
t2.component.html
<h1>
  {{title | json}} 
</h1>
<form (ngSubmit)="onSubmit(form.value)" [ngFormModel]="form">
	<ul>
		<li>
			<label for="medium">Medium</label>
			<select name="medium" id="medium" ngControl="medium">
				<option *ngFor="let medium of lookupLists.mediums" value="{{medium}}">{{medium}}</option>
			</select>
		</li>
		<li>
			<label for="name">Name</label>
			<input type="text" name="name" id="name" ngControl="name"
			  ngControl="name" 
			  #name="ngForm"
			/>
			<div *ngIf="name.errors?.pattern" class="error">name is invalid</div>
		</li>
		<li>
			<label for="year">Year</label>
			<input type="year" name="year" id="year" ngControl="year"
			  ngControl="year"
			  #year="ngForm"
			/>
			<div *ngIf="year.errors?.year" class="error">must be between {{year.errors?.year.min}} and {{year.errors?.year.max}}</div>
		</li>
	</ul>
	<button type="submit" [disabled]="!form.valid ">Save</button>
	<p>
		{{mediaItems | json}}
	</p>
</form>
my-service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class MyServiceService {

  constructor() {}
  get(){
  	return this.mediaItems;
  }
  add(mediaItem){
  	this.mediaItems.push(mediaItem);
  }
  delete(mediaItem){
  	var index = this.mediaItems.indexOf(mediaItem);
  	if(index >= 0){
  		this.mediaItems.splice(index,1);
  	}
  }
  mediaItems = [
        {
            id: 1,
            name: "Firebug",
            medium: "Series",
            category: "Science Fiction",
            year: 2010,
            watchedOn: 1294166565384,
            isFavorite: false
        },
        {
            id: 2,
            name: "The Small Tall",
            medium: "Movies",
            category: "Comedy",
            year: 2015,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 3,
            name: "The Redemption",
            medium: "Movies",
            category: "Action",
            year: 2016,
            watchedOn: null,
            isFavorite: false
        }, {
            id: 4,
            name: "Hoopers",
            medium: "Series",
            category: "Drama",
            year: null,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 5,
            name: "Happy Joe: Cheery Road",
            medium: "Movies",
            category: "Action",
            year: 2015,
            watchedOn: 1457166565384,
            isFavorite: false
        }
    ];
}
main.ts
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode, provide } from '@angular/core';
import { T3AppComponent, environment } from './app/';
import { MyServiceService } from './app/my-service.service';

var lookupLists = {
	mediums: ['Movies', 'Series']	
}

if (environment.production) {
  enableProdMode();
}

bootstrap(T3AppComponent,[
	MyServiceService,
	provide('LOOKUP_LISTS',{useValue: lookupLists})
]);

typescript NG2-进步,bar.ts

ng2-progress-bar.ts
import {Component, ViewEncapsulation, ElementRef, Inject, EventEmitter, Output, ViewChild} from 'angular2/core';
import {Input} from 'angular2/core';
import {ChangeDetectionStrategy} from 'angular2/core';
import {DatePipe} from 'angular2/common';
import {PlayerDurationPipe} from './playerDurationPipe';
import {Observable} from 'rxjs/Observable';
import {Subject}  from 'rxjs/Subject';
import {Action} from '@ngrx/store';
import {FilmStrip} from './filmStrip/filmStrip';
export interface ScrubberHoverEvent {
  pixels: number;
  seconds: number;
}

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'progress-bar',
  directives: [FilmStrip],
  pipes: [DatePipe, PlayerDurationPipe],
  host: {
    '(mouseenter)': 'showScrubberHandle()',
    '(mouseleave)': 'hideScrubberHandle()',
    '(mousemove)': 'scrubberHoverEvent.next($event)'
  },
  template: `
    <film-strip *ngIf="!state.isLive && state.filmStripUrl" [filmStripUrl]="state.filmStripUrl" [class.visible]="scrubberHandleVisibility" [position]="scrubberHoverEvent" [style.left]="scrubberHandlePosition | async" ></film-strip>
    <div #scrubber class="scrubber" (click)="changePosition.next($event)">
      <div class="scrubber-handle" [class.visible]="scrubberHandleVisibility" [style.left]="scrubberHandlePosition | async"></div>
      <div class="progress-container" [style.width]="progress"></div>
      <div class="buffer-container" [style.width]="buffer"></div>
    </div>
    <span class="remaining">{{remaining | playerDuration}}</span>
  `,
  styles: [
    require('./progressBar.scss')
  ]
})
/**
 * @description Manages the playback location and filmstrip.
 * @class ProgressBar
 */
export class ProgressBar {
  /**
   * @description The amount loaded in percent
   * @type {string} eg. '100%'
   */
  buffer: string;
  /**
   * @description The amount watched or position in the media.
   * @type {string} eg. '10%'
   */
  progress: string;
  /**
   * @description The amount left to watch in miliseconds
   * @type {number} eg. 10000
   */
  remaining: number;

  /**
   * @description The Hover over the progress bar event stream, maps the raw dom event to a ScrubberHoverEvent.
   * @type {Observable<ScrubberHoverEvent>}
   */
  scrubberHoverEvent: Observable<any>;

  /**
   * @description Observes the hover event and maps it to a pixel value where the scrubber handle should be positioned.
   * @type {Observable<string>}
   */
  scrubberHandlePosition: Observable<string>;

  /**
   * @description Listens to the click on the scrubber bar and tells the component to emit the latest value from the
   *   scrubber hover
   * @type {Observable<ScrubberHoverEvent>}
   */
  changePosition: Observable<any>;

  /**
   * @description Internal state of the scrubber handle. TODO - LOW -> map this into an observable
   * @type {boolean}
   */
  scrubberHandleVisibility: boolean = false;

  /**
   * @description The player state input (important :)
   * @type {PlayerState}
   */
  @Input()
  state;

  /**
   * @description Events coming out of this component
   * @type {EventEmitter}
   */
  @Output()
  progressBarEvent: EventEmitter<Action> = new EventEmitter();

  /**
   * @description The scrubber bar element
   * @type {ElementRef}
   */
  @ViewChild('scrubber')
  scrubber: ElementRef;

  ngOnInit() {
    //Listen to MouseMove
    this.scrubberHoverEvent = new Subject().map((event: MouseEvent): ScrubberHoverEvent => {
      //The bounded position
      let pos = Math.min(Math.max(0, event.offsetX), this.scrubber.nativeElement.offsetWidth);
      //The ScrubberHoverEvent
      return {
        seconds: (pos / this.scrubber.nativeElement.offsetWidth) * this.state.duration, //Millisecond position
        pixels: pos //Pixel position on page
      };
    });

    this.scrubberHandlePosition = this.scrubberHoverEvent
      .map(
        (data: ScrubberHoverEvent) => {
          return `${data.pixels}px`;
        }
      );

    //Listen to the click and grab the last value from scrubberHover.
    this.changePosition = new Subject().withLatestFrom(this.scrubberHoverEvent, (click: MouseEvent, hover: ScrubberHoverEvent): ScrubberHoverEvent => {
      return hover;
    });

    this.changePosition.subscribe((data: ScrubberHoverEvent): void => {
      this.progressBarEvent.emit({type: 'seek', payload: data.seconds});
    });
  }

  ngOnChanges() {
    //When the input changes recalc the percentages.
    this.buffer = `${this.state.percentLoaded}%`;
    this.progress = `${this.state.percentWatched}%`;
    this.remaining = this.state.duration - this.state.position;
  }

  showScrubberHandle() {
    this.scrubberHandleVisibility = true;
  }

  hideScrubberHandle() {
    this.scrubberHandleVisibility = false;
  }
}

typescript DI - 建立服务

DI - 建立服务

t2.component.ts
import { Component } from '@angular/core';
import { Control, Validators, FormBuilder } from '@angular/common';
import { MyServiceService } from './my-service.service'

@Component({
  moduleId: module.id,
  selector: 't3-app',
  templateUrl: 't3.component.html',
  styleUrls: ['t3.component.css'],
  providers:[MyServiceService]
})
export class T3AppComponent {
  form;
  mediaItems:any;
  
  constructor(private formBuilder: FormBuilder,
  	private myServiceService: MyServiceService
  ) {}

  ngOnInit(){
  	this.mediaItems = this.myServiceService.get();

  	this.form = this.formBuilder.group({
  	  'medium': new Control('Movies'),
  	  'name': new Control('',Validators.compose([
  	  	Validators.required,
  	  	Validators.pattern('[\\w\\-\\s\\/]+'),
  	  ])),
  	  'year': new Control('',this.yearValidation)
  	});
  }

  yearValidation(control){
  	if(control.value.trim().length === 0) return null;
  	var year = parseInt(control.value);
  	var minYear = 2000;
  	var maxYear = 2010;
  	if(year >= minYear && year <= maxYear) return null;
  	return {'year': {'min':minYear, 'max':maxYear}}
  }

  title:any;
  
  onSubmit(data){
  	this.title = data;
  }
}
t2.component.html
<h1>
  {{title | json}} 
</h1>
<form (ngSubmit)="onSubmit(form.value)" [ngFormModel]="form">
	<ul>
		<li>
			<label for="medium">Medium2</label>
			<select name="medium" id="medium" ngControl="medium">
				<option value="Movies">Movies</option>
				<option value="Series">Series</option>
			</select>
		</li>
		<li>
			<label for="name">Name</label>
			<input type="text" name="name" id="name" ngControl="name"
			  ngControl="name" 
			  #name="ngForm"
			/>
			<div *ngIf="name.errors?.pattern" class="error">name is invalid</div>
		</li>
		<li>
			<label for="year">Year</label>
			<input type="year" name="year" id="year" ngControl="year"
			  ngControl="year"
			  #year="ngForm"
			/>
			<div *ngIf="year.errors?.year" class="error">must be between {{year.errors?.year.min}} and {{year.errors?.year.max}}</div>
		</li>
	</ul>
	<button type="submit" [disabled]="!form.valid ">Save</button>
	<p>
		{{mediaItems | json}}
	</p>
</form>
my-service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class MyServiceService {

  constructor() {}
  get(){
  	return this.mediaItems;
  }
  add(mediaItem){
  	this.mediaItems.push(mediaItem);
  }
  delete(mediaItem){
  	var index = this.mediaItems.indexOf(mediaItem);
  	if(index >= 0){
  		this.mediaItems.splice(index,1);
  	}
  }
  mediaItems = [
        {
            id: 1,
            name: "Firebug",
            medium: "Series",
            category: "Science Fiction",
            year: 2010,
            watchedOn: 1294166565384,
            isFavorite: false
        },
        {
            id: 2,
            name: "The Small Tall",
            medium: "Movies",
            category: "Comedy",
            year: 2015,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 3,
            name: "The Redemption",
            medium: "Movies",
            category: "Action",
            year: 2016,
            watchedOn: null,
            isFavorite: false
        }, {
            id: 4,
            name: "Hoopers",
            medium: "Series",
            category: "Drama",
            year: null,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 5,
            name: "Happy Joe: Cheery Road",
            medium: "Movies",
            category: "Action",
            year: 2015,
            watchedOn: 1457166565384,
            isFavorite: false
        }
    ];
}

typescript DI - 类构造函数注入

DI - 类构造函数注入

t2.component.ts
import { Component } from '@angular/core';
import { Control, Validators, FormBuilder } from '@angular/common';
@Component({
  moduleId: module.id,
  selector: 't3-app',
  templateUrl: 't3.component.html',
  styleUrls: ['t3.component.css']
})
export class T3AppComponent {
  form;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit(){
  	this.form = this.formBuilder.group({
  	  'medium': new Control('Movies'),
  	  'name': new Control('',Validators.compose([
  	  	Validators.required,
  	  	Validators.pattern('[\\w\\-\\s\\/]+'),
  	  ])),
  	  'year': new Control('',this.yearValidation)
  	});
  }

  yearValidation(control){
  	if(control.value.trim().length === 0) return null;
  	var year = parseInt(control.value);
  	var minYear = 2000;
  	var maxYear = 2010;
  	if(year >= minYear && year <= maxYear) return null;
  	return {'year': {'min':minYear, 'max':maxYear}}
  }

  title:any;
  
  onSubmit(data){
  	this.title = data;
  }
}

typescript 角管 - 定制

角管 - 定制

t1.component.ts
import { Component } from '@angular/core';
import { MyPipe } from './my-pipe.pipe';

@Component({
  moduleId: module.id,
  selector: 't1-app',
  pipes:[MyPipe],
  templateUrl: 't1.component.html',
  styleUrls: ['t1.component.css']
})
export class T1AppComponent {
  title = 't1 works!';

  mediaItems = [
        {
            id: 1,
            name: "Firebug",
            medium: "Series",
            category: "Science Fiction",
            year: 2010,
            watchedOn: 1294166565384,
            isFavorite: false
        },
        {
            id: 2,
            name: "The Small Tall",
            medium: "Movies",
            category: "Comedy",
            year: 2015,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 3,
            name: "The Redemption",
            medium: "Movies",
            category: "Action",
            year: 2016,
            watchedOn: null,
            isFavorite: false
        }, {
            id: 4,
            name: "Hoopers",
            medium: "Series",
            category: "Drama",
            year: null,
            watchedOn: null,
            isFavorite: true
        }, {
            id: 5,
            name: "Happy Joe: Cheery Road",
            medium: "Movies",
            category: "Action",
            year: 2015,
            watchedOn: 1457166565384,
            isFavorite: false
        }
    ];
}
t1.component.html
<h1>
  {{title}}
</h1>
<div>
	{{mediaItems | myPipe}}
</div>
my-pipe.pipe.ts
import { Pipe } from '@angular/core';

@Pipe({
  name: 'myPipe'
})

export class MyPipe {
  transform(mediaItems) {
    var categories = [];
    mediaItems.forEach(mediaItem => {
    	if(categories.indexOf(mediaItem.category) <= -1){
    		categories.push(mediaItem.category);
    	}
    });

    return categories.join(', ');
  }
}