import { Component, ChangeDetectionStrategy } from '@angular/core';

import { IGrid, ICell } from '../interfaces/models.ts';

import { CellDataService } from '../services/celldataservice.ts';

@Component({
  selector: 'my-app',
  template: `<my-grid [data]=grid class="grid"></my-grid>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})


export class AppComponent { 
  name = 'Angular 123'; 
  private grid: IGrid;
  
  constructor(cellDataService: CellDataService) {
    this.grid =  cellDataService.getGridState();
    // console.log("app - grid", this.grid.cells);
  }
}
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';

import {CellDataService} from '../services/celldataservice.ts';

import { GridComponent } from '../components/grid.ts';
import { CellComponent } from '../components/cell.ts';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ 
    AppComponent,
    GridComponent,
    CellComponent
  ],
  providers: [CellDataService],
  bootstrap: [ AppComponent ]
})


export class AppModule { }

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

<!DOCTYPE html>
<html>
  <head>
    <title>Angular Quickstart</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    * {
        box-sizing: border-box;
      }
      
      .hide {
        display: none;
      }
    
      body {
        background:#1c1d1f; 
        color: #FFF;
        font-size: 1em;
        font-family: Helvetica,Georgia,sans-serif;
        margin-left:20px;
      }
      
      h1 {
        font-size: 1.3em;
        color:#FFC600;
      }
      
      p {
        font-size: 0.9em;
        color: #FFF;
        font-family: consolas,sans-serif;
      }
      
      .app {
        perspective: 500px;
        display: block;
      }
    </style>

    <!-- Polyfills -->
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.8.4?main=browser"></script>
    <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
    <script src="systemjs.config.js"></script>
    <script>
      System.import('main.js').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <h1>OnPush Change Detection</h1>
    <p> Click on the cell to visualize change detection with onpush ChangeDetectionStrategy.
         Border appears around the cell when Angular runs change detection for that cell.</p>
    <my-app class="app">loading...</my-app>
  </body>

</html>

(function (global) {
  System.config({
    transpiler: 'ts',
    typescriptOptions: {
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": false,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "lib": ["es2015", "dom"],
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      'npm:': 'https://unpkg.com/'
    },
    map: {
      'app': 'app',

      '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
      '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      'rxjs':                      'npm:rxjs@5.0.1',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript@5.2.7/lib/plugin.js',
      'typescript':                'npm:typescript@2.2.1/lib/typescript.js',
      'lodash':                    'npm:lodash@4.17.4/lodash.js',

    },
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts',
        meta: {
          './*.ts': {
            loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });

})(this);

var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;

module.exports.translate = function(load){
  if (load.source.indexOf('moduleId') != -1) return load;

  var url = document.createElement('a');
  url.href = load.address;

  var basePathParts = url.pathname.split('/');

  basePathParts.pop();
  var basePath = basePathParts.join('/');

  var baseHref = document.createElement('a');
  baseHref.href = this.baseURL;
  baseHref = baseHref.pathname;

  if (!baseHref.startsWith('/base/')) { // it is not karma
    basePath = basePath.replace(baseHref, '');
  }

  load.source = load.source
    .replace(templateUrlRegex, function(match, quote, url){
      let resolvedUrl = url;

      if (url.startsWith('.')) {
        resolvedUrl = basePath + url.substr(1);
      }

      return 'templateUrl: "' + resolvedUrl + '"';
    })
    .replace(stylesRegex, function(match, relativeUrls) {
      var urls = [];

      while ((match = stringRegex.exec(relativeUrls)) !== null) {
        if (match[2].startsWith('.')) {
          urls.push('"' + basePath + match[2].substr(1) + '"');
        } else {
          urls.push('"' + match[2] + '"');
        }
      }

      return "styleUrls: [" + urls.join(', ') + "]";
    });

  return load;
};
import { Component, Input, OnInit, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core';

import { IGrid } from '../interfaces/models.ts';

@Component({
  selector: 'my-grid',
  templateUrl: './components/grid.html',
  styleUrls: [ './components/grid.css' ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GridComponent implements OnInit {
  @Input() data: IGrid;
  
  constructor() { }

  ngOnInit(): void {
    
  }
}
export interface IGrid {
  cells: Cell[];
  rows: number;
  columns: number;
}

// Angular CLI had a bug which prevents to export multiple interfaces
// using class
export class ICell {
  id: number;
  flipped: boolean;
  text: string;
  changed: boolean;
}


/**
 * Bitmask of states
 * https://github.com/angular/angular/blob/39b92f7e546b8bdecc0e08c915d36717358c122b/packages/core/src/view/types.ts
 */
export const enum ViewState {
  BeforeFirstCheck = 1 << 0,
  FirstCheck = 1 << 1,
  Attached = 1 << 2,
  ChecksEnabled = 1 << 3,
  IsProjectedView = 1 << 4,
  CheckProjectedView = 1 << 5,
  CheckProjectedViews = 1 << 6,
  Destroyed = 1 << 7,

  CatDetectChanges = Attached | ChecksEnabled,
  CatInit = BeforeFirstCheck | CatDetectChanges
}

import { Component, 
  Input, 
  OnInit, 
  AfterViewChecked, 
  ViewEncapsulation, 
  NgZone, 
  ElementRef, 
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  DoCheck } from '@angular/core';

import { ICell, ViewState } from '../interfaces/models.ts';

import {CellDataService} from '../services/celldataservice.ts';

@Component({
  selector: 'my-cell',
  templateUrl: './components/cell.html',
  styleUrls: [ './components/cell.css' ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CellComponent implements OnInit, AfterViewChecked, DoCheck  {
  @Input() data: ICell;
  
  constructor(
    private cellDataService: CellDataService,
    private zone: NgZone,
    private el:ElementRef,
    private ref: ChangeDetectorRef) { }

  ngOnInit(): void {
    
  }
  
  ngAfterViewChecked() {
    // "ngAfterViewChecked" is always getting executed (irrespective of Default or OnPush) making it not useful to trigger the visualization.
    // Angular had a bug on this - https://github.com/angular/angular/issues/7055
    // Using a work-around the visualize the change detection.
  }
  
  ngDoCheck() {
    
  }
  
  onClick(): void {
    this.cellDataService.flipCell(this.data.id);
    this.zone.runOutsideAngular(() => {
        setTimeout(() => {
          // Since Angular doesn't detect any of these changes (due to onpush),
          // Reset the 'changed' state outside Angular
          const elem = document.querySelectorAll(`[data-id="${this.data.id}"]`)[0];
          if (!elem) { return;}
          elem.classList.remove('changed');
          this.cellDataService.resetChanged();
        }, 1200);
    });
  }
}
<my-cell *ngFor="let cell of data.cells"  
  [data]=cell class="cell" 
  [class.flip]="cell.flip"></my-cell>
<div [attr.data-id]="data.id"
  (click)="onClick()"
  [class.changed]="data.changed">
  <div class="front"></div>
  <div class="back">{{data.text}}</div>
</div>
.grid {
  width: 900px;
  display: block;

  position:absolute;
  left:170px;
  top: 30px;

  transform: rotateX(0deg) rotateY(10deg) rotateZ(0deg);
  transform-style: preserve-3d;
}
.cell {
  width:30px;
  height:30px;
  float:left;
  margin:0 5px 5px 0;
  cursor:pointer;
  display: block;
  transition: all 0.4s;
  transform-style: preserve-3d;
  position: relative;
}

.cell.flip {
  transform: rotateY(180deg);
}

.front {
  background:#75A2A2;
}

.back {
  background:#FFC600;
  transform: rotateY(180deg);
  box-shadow:10px 10px 10px rgba(0,0,0,0.5);
  color: black;
  text-align: center;
  padding-top:25%;
}

.front, .back {
  width:100%;
  height:100%;
	-webkit-backface-visibility: hidden;
	backface-visibility: hidden;
	position: absolute;
	top: 0;
	left: 0;
}

.changed .front {
  border: 3px dashed #FFC600;
}

.changed .back {
  border: 3px dashed #75A2A2;
}


/*

.cell {
  width:30px;
  height:30px;
  float:left;
  margin:0 5px 5px 0;
  cursor:pointer;
  display: block;
}

.cell:before{
  position:absolute;
  content:'';
  width:30px;
  height:30px;
  background:#75A2A2;
  transition: all 0.4s;
  pointer-events: none;
  transform-style: preserve-3d;
}

.cell:hover:before{
  
  background:#FFC600;
  transform: rotateY(180deg);
  box-shadow:10px 10px 10px rgba(0,0,0,0.5);
}

*/
//import { Hero } from './hero';
import { Injectable } from '@angular/core';
import * as _ from 'lodash';

import { IGrid, ICell } from '../interfaces/models.ts';

@Injectable()
export class CellDataService {

  readonly TOTAL: number = 500;
  
  private currentState: IGrid;
   
  constructor() {
    this.currentState = {
      cells:_.range(this.TOTAL).map(n => {
        return <ICell> {
          id: n,
          flip: false,
          text: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"[Math.floor(Math.random() * 36)],
          changed: false
        };
      })
    };
    console.log(this.currentState);
  }
  
  getGridState(): IGrid {
    return this.currentState;
  }
  
  flipCell(id): void {
    console.log(id);
    let index: number = _.findIndex(this.currentState.cells, c => c.id === id);
    let updatedCell = Object.assign({}, this.currentState.cells[index], {
      flip: !this.currentState.cells[index].flip
    });
    
    this.currentState.cells = [
      ...this.currentState.cells.slice(0, index),
      updatedCell,
      ...this.currentState.cells.slice(index + 1)
      ];
      
    // To trigger the 'changed' visualization
    _.each(this.currentState.cells, c => { c.changed = true; })
  }
  
  resetChanged(): void {
    // To reset the 'changed' visualization
    _.each(this.currentState.cells, c => { c.changed = false; })
  }
  
  addCells(count: number): IGrid {
    
  }
  
  removeCells(count: number): IGrid {
    
  }
}



/*
export interface IGrid {
  cells: Cell[];
  rows: number;
  columns: number;
}

// Angular CLI had a bug which prevents to export multiple interfaces
// using class
export class ICell {
  id: number;
  width: number;
  height: number;
  flipped: boolean;
}

*/