import {Component, AfterViewInit, ElementRef, OnInit, QueryList, ViewChildren} from 'angular2/core';

import {Hero} from './hero';

@Component({
  selector: 'my-app',
  templateUrl: 'app/app.component.html'
})
export class AppComponent implements AfterViewInit, OnInit {

  ngOnInit(){
    this.refreshHeroes();
  }

  ngAfterViewInit() {
    this._detectNgForTrackByEffects();
  }

  heroes:Hero[];

  heroesTrackBy(index: number, hero: Hero) { return hero.id; }

  idTrackBy(index: number, item: any): string { return item['id']; }

 // update this.heroes with fresh set of cloned heroes
  refreshHeroes() {
    this.heroes = Hero.MockHeroes.map(hero => Hero.clone(hero));
  }

  get sameAsItEverWas() {
    return [1,2,3,4,5].map(id => {
      return {id:id, text: 'same as it ever was ...'};
    });
  }

  //////// Detect effects of NgForTrackBy ///////////////
  @ViewChildren('noTrackBy') childrenNoTrackBy:QueryList<ElementRef>;
  @ViewChildren('withTrackBy') childrenWithTrackBy:QueryList<ElementRef>;

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

  heroesNoTrackByChangeCount = 0;
  heroesWithTrackByChangeCount = 0;

  private _detectNgForTrackByEffects() {
    this._oldNoTrackBy = toArray(this.childrenNoTrackBy);
    this._oldWithTrackBy = toArray(this.childrenWithTrackBy);

    this.childrenNoTrackBy.changes.subscribe((changes:any) => {
      let newNoTrackBy = toArray(changes);
      let isSame = this._oldNoTrackBy.every((v:any, i:number) => v === newNoTrackBy[i]);
      if (!isSame) { this.heroesNoTrackByChangeCount++ }
    })

    this.childrenWithTrackBy.changes.subscribe((changes:any) => {
      let newWithTrackBy = toArray(changes);
      let isSame = this._oldWithTrackBy.every((v:any, i:number) => v === newWithTrackBy[i]);
      if (!isSame) { this.heroesWithTrackByChangeCount++ }
    })
  }
  ///////////////////

}

// helper to convert viewChildren to an array of HTMLElements
function toArray(viewChildren:QueryList<ElementRef>) {
  let result: HTMLElement[] = [];
  let children = viewChildren.toArray()[0].nativeElement.children;
  for (var i = 0; i < children.length; i++) { result.push(children[i]); }
  return result;
}

/*
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
*/
export class Hero {
  public id:number

  constructor(
    public firstName:string,
    public lastName?:string,
    public birthdate?:Date,
    public url?:string,
    public rate:number = 100,
    id?:number) {
      this.id = id != null ? id : Hero.nextId++;
    }

  static clone({firstName, lastName, birthdate, url, rate, id} : Hero){
    return new Hero (firstName, lastName, birthdate, url, rate, id );
  }

  get fullName() {return `${this.firstName} ${this.lastName}`;}

  static nextId = 1;

  static MockHeroes = [
    new Hero(
      'Hercules',
      'Son of Zeus',
      new Date(1970, 1, 25),
      'http://www.imdb.com/title/tt0065832/',
      325),

    new Hero('eenie', 'toe'),
    new Hero('Meanie', 'Toe'),
    new Hero('Miny', 'Toe'),
    new Hero('Moe', 'Toe')
  ];
}

/*
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
*/
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);

/*
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
*/
fieldset {border-style:none}
img {height: 100px;}
.box {border: 1px solid black; padding:3px}
.child-div {margin-left: 1em; font-weight: normal}
.hidden {display: none}
.parent-div {margin-top: 1em; font-weight: bold}
.special {font-weight:bold; font-size: x-large}
.bad {color: red;}
.curly {font-family: "Brush Script MT"}
.toe {margin-left: 1em; font-style: italic;}
little-hero {color:blue; font-size: smaller; background-color: Turquoise }
.to-toc {margin-top: 10px; display: block}

/*
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
*/
<!--
 Beta.3 bug reported ... with Kara's workaround described here
 https://github.com/angular/angular/issues/6936#issuecomment-180962654
-->
<h2 id="ngFor-trackBy">NgForTrackBy</h2>
<button (click)="refreshHeroes()">Refresh heroes</button>
<p>First hero: <input [(ngModel)]="heroes[0].firstName"></p>

<p>without trackBy</p>
<div #noTrackBy class="box">
  <div *ngFor="#hero of heroes">({{hero.id}}) {{hero.fullName}}</div>
</div>
<div id="noTrackByCnt" *ngIf="heroesNoTrackByChangeCount">
  Hero DOM elements change #{{heroesNoTrackByChangeCount}} without trackBy
</div>

<p>with trackBy and semi-colon separator</p>
<div #withTrackBy class="box">
  <!-- <div *ngFor="#hero of heroes; #i=index; trackBy:heroesTrackBy">({{hero.id}}) {{hero.fullName}}</div> -->
  <div *ngFor="#hero of heroes; #i=index; trackBy:heroesTrackBy">({{heroes[i].id}}) {{heroes[i].fullName}}</div>
</div>
<div id="withTrackByCnt" *ngIf="heroesWithTrackByChangeCount">
  Hero DOM elements change #{{heroesWithTrackByChangeCount}} with trackBy
</div>

<p>with trackBy and comma separator</p>
<div class="box">
  <!-- <div *ngFor="#hero of heroes, #i=index, trackBy:heroesTrackBy">({{hero.id}}) {{hero.fullName}}</div> -->
  <div *ngFor="#hero of heroes, #i=index, trackBy:heroesTrackBy">({{heroes[i].id}}) {{heroes[i].fullName}}</div>
</div>

<p>with *ngForTrackBy</p>
<div class="box">
  <!-- <div *ngFor="#hero of heroes; #i=index" *ngForTrackBy="heroesTrackBy">({{hero.id}}) {{hero.fullName}}</div> -->
  <div *ngFor="#hero of heroes; #i=index" *ngForTrackBy="heroesTrackBy">({{heroes[i].id}}) {{heroes[i].fullName}}</div>
</div>

<p>with generic id trackBy function</p>
<div class="box">
  <!--  <div *ngFor="#item of sameAsItEverWas; #i=index" *ngForTrackBy="idTrackBy">({{item.id}}) {{item.text}}</div> -->
  <div *ngFor="#item of sameAsItEverWas; #i=index" *ngForTrackBy="idTrackBy">({{heroes[i].id}}) {{heroes[i].fullName}}</div>
</div>

<!-- 
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
-->
<!DOCTYPE html>
<html>
  <head>
    <title>Template Syntax</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    
    <!-- IE required polyfills, in this exact order -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.16/system-polyfills.js"></script>
    
    <script src="https://code.angularjs.org/2.0.0-beta.3/angular2-polyfills.js"></script>
    <script src="https://code.angularjs.org/tools/system.js"></script>
    <script src="https://code.angularjs.org/tools/typescript.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.3/Rx.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.3/angular2.dev.js"></script>
    <script>
      System.config({
        transpiler: 'typescript', 
        typescriptOptions: { emitDecoratorMetadata: true }, 
        packages: {'app': {defaultExtension: 'ts'}} 
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </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
-->