<!DOCTYPE html>
<html>

<head>
  <title>angular2 playground</title> 
  <!-- Set the base href -->
  <script>document.write('<base href="' + document.location + '" />');</script>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Polyfill(s) for older browsers -->
  <script src="https://npmcdn.com/core-js/client/shim.min.js"></script>
  <script src="https://npmcdn.com/zone.js@0.6.12?main=browser"></script>
  <script src="https://npmcdn.com/reflect-metadata@0.1.3"></script>
  <script src="https://npmcdn.com/systemjs@0.19.27/dist/system.src.js"></script>
  <script src="systemjs.config.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
</head>

<body>
  <root>
    loading...
  </root>
</body>

</html>
/* Styles go here */

.active {
  text-decoration: default;
}

.inactive {
  text-decoration: none; 
}
### Angular2 Todo List using Redux - Typescript - Beta 0

Features:
- Initial port from Redux video course by Dan Abramov. Note some parts divert from the original.
- Added Pipe to filter todos.
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  //map tells the System loader where to look for things
  map: {
    app: "./src",
    'redux': 'https://cdnjs.cloudflare.com/ajax/libs/redux/3.0.4/redux.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    }
  }
});
//our root app component
import {Component} from '@angular/core'

@Component({
  selector: 'root', 
  template: 
    `<div>
      <add-todo></add-todo>
      <todo-list></todo-list>
      <filters></filters>
    </div>`
})
export class App { }
import {Component, Inject} from '@angular/core';
import {TodoActions} from './todoActions';

@Component({
  selector: 'add-todo', 
  template: 
    `<div>
      <input #todo>
      <button (click)="addTodo(todo)">Add todo</button>
    </div>`
})
export class AddTodo {
  constructor(
    @Inject('AppStore') private appStore: AppStore, 
    private todoActions: TodoActions
  ){ }
  
  private addTodo(input) {
    this.appStore.dispatch(this.todoActions.addTodo(input.value));
    input.value = '';
  }
}
import {Component, Inject} from '@angular/core';

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <todo 
        *ngFor="let todo of todos | visibleTodos:currentFilter"
        [completed]="todo.completed"
        [id]="todo.id"
      >{{todo.text}}</todo>
    </ul>
  `
})
export class TodoList implements OnDestroy {
  constructor(
    @Inject('AppStore') private appStore: AppStore
  ){
    this.unsubscribe = this.appStore.subscribe(()=> {
      let state = this.appStore.getState();
      this.currentFilter = state.currentFilter;
      this.todos = state.todos;
    });
  }
  
  private ngOnDestroy(){
    //remove listener
    this.unsubscribe();
  }
}

import {Component} from '@angular/core'

@Component({
  selector: 'filters',
  template: 
    '<p>Show: ' +
      '<filter-link filter="SHOW_ALL">All</filter-link>, ' + 
      '<filter-link filter="SHOW_ACTIVE">Active</filter-link>, ' +
      '<filter-link filter="SHOW_COMPLETED">Completed</filter-link>' +
    '</p>'
})
export class Filters { }
import {Component, ContentChildren, Inject, ChangeDetectionStrategy} from '@angular/core';
import {TodoActions} from './todoActions';

@Component({
  selector: 'filter-link',
  inputs: ['filter'],
  template: 
    `<a href="#" (click)="applyFilter(filter);"
        [ngClass]="{'active': active, 'inactive': !active}">` +
      `<ng-content></ng-content>` +  
    `</a>`
})
export class FilterLink implements OnInit, OnDestroy {
  constructor(
    @Inject('AppStore') private appStore: AppStore, 
    private todoActions: TodoActions
  ){
    this.unsubscribe = this.appStore
      .subscribe(() => this.updateActive());
  }
  
  private ngOnInit(){
    //set initial state
    this.updateActive();
  }
  
  private ngOnDestroy(){
    //remove change listener
    this.unsubscribe();
  }
  
  // Helper methods
  private applyFilter(filter) {
    this.appStore.dispatch(this.todoActions.setCurrentFilter(filter));
  }
  
  private updateActive(){
    this.active = (this.filter == this.appStore.getState().currentFilter);
  }
}
import {Component, ContentChildren, Inject, ChangeDetectionStrategy} from '@angular/core';
import {TodoActions} from './todoActions';

@Component({
  selector: 'todo',
  inputs: ['completed', 'id'],
  template: `
    <li (click)="onTodoClick(id)"
      [style.textDecoration]="completed?'line-through':'none'">
      <ng-content></ng-content>
    </li> 
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Todo { 
  constructor(
    @Inject('AppStore') private appStore: AppStore, 
    private todoActions: TodoActions
  ){ }
  
  private onTodoClick(id){
    this.appStore.dispatch(this.todoActions.toggleTodo(id));
  }
  
  private removeTodo(id){
    this.appStore.dispatch(this.todoActions.removeTodo(id));
  }
}
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const SET_CURRENT_FILTER = 'SET_CURRENT_FILTER';

export class TodoActions {
  constructor() {
    this.nextToDoId = 0;
  }
  
  addTodo(text){  
    return {
      type: ADD_TODO,
      id: this.nextToDoId++,
      text: text,
      completed: false
    };
  };
  
  toggleTodo(id){
    return {
      type: TOGGLE_TODO,
      id: id
    };
  };
  
  removeTodo(id){
    return {
      type: REMOVE_TODO,
      id: id
    };    
  }
  
  setCurrentFilter(filter){
    return {
      type: SET_CURRENT_FILTER,
      filter: filter
    };
  };
}
import * as TodoActions from './todoActions';

const initialState = {
  todos: [],
  currentFilter: 'SHOW_ALL'
}

export function rootReducer(state = initialState, action){
  switch (action.type) {
    case TodoActions.ADD_TODO: 
      return {
        todos: state.todos.concat({
          id: action.id,
          text: action.text,
          completed: action.completed
        }),
        currentFilter: state.currentFilter
      };
    case TodoActions.TOGGLE_TODO:
      return {
        todos: toggleTodo(state.todos, action),
        currentFilter: state.currentFilter
      };
    case TodoActions.REMOVE_TODO:
      return {
        todos: state.todos.filter(todo => todo.id != action.id),
        currentFilter: state.currentFilter
      };
    case TodoActions.SET_CURRENT_FILTER:
      return { 
        todos: state.todos.map(todo => todo),
        currentFilter: action.filter
      };
    default:
      return state;
  }
};

function toggleTodo(todos, action){
  //map returns new array
  return todos.map(todo => {
    //skip other items
    if (todo.id !== action.id)
      return todo;
    //toggle
    return {
      id: todo.id,
      text: todo.text,
      completed: !todo.completed
    };
  });
}
import {isBlank, isPresent, isArray} from '@angular/core/src/facade/lang';
import {BaseException} from '@angular/core/src/facade/exceptions';
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'visibleTodos'
})
export class VisibleTodosPipe implements PipeTransform {
  transform(todos, filter){ 
    if (!todos) return;
    if (filter.length == 0) {
      throw new BaseException('VisibleTodos pipe requires one argument');
    }
    if (isPresent(todos) && !isArray(todos)){
      throw new BaseException('VisibleTodos pipe requires an Array as input');
    }
    return this.getVisibleTodos(todos, filter);
  }
  
  private getVisibleTodos(todos, filter){
    switch (filter) {
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed);
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed);
      case 'SHOW_ALL':
      default:
        return todos;
    }
  };
}
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  }
}
/**
 * PLUNKER VERSION (based on systemjs.config.js in angular.io)
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {

  var ngVer = '@2.0.0-rc.5'; // lock in the angular package version; do not let it float to current!
  var routerVer = '@3.0.0-rc.1'; // lock router version
  var formsVer = '@0.3.0'; // lock forms version
  var routerDeprecatedVer = '@2.0.0-rc.2'; // temporarily until we update all the guides

  //map tells the System loader where to look for things
  var map = {
    'app':                        'src',

    '@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,
    '@angular/router-deprecated': 'https://npmcdn.com/@angular/router-deprecated' + routerDeprecatedVer,
    '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',
    'typescript':                 'https://npmcdn.com/typescript@1.9.0-dev.20160409/lib/typescript.js',
    'redux':                      'https://cdnjs.cloudflare.com/ajax/libs/redux/3.0.4/redux.js',
 };

  //packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'main.ts',  defaultExtension: 'ts' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
    'src':                        { defaultExtension: 'ts' },
  };

  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'upgrade',
  ];

  // Add map entries for each angular package
  // only because we're pinning the version with `ngVer`.
  ngPackageNames.forEach(function(pkgName) {
    map['@angular/'+pkgName] = 'https://npmcdn.com/@angular/' + pkgName + ngVer;
  });

  // Add package entries for angular packages
  ngPackageNames.concat(['forms', 'router', 'router-deprecated']).forEach(function(pkgName) {

    // Bundled (~40 requests):
    packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };

    // Individual files (~300 requests):
    //packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
  });

  var config = {
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      tsconfig: true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    map: map,
    packages: packages
  };

  System.config(config);

})(this);


/*
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 { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import {App} from './app';
import {createStore} from 'redux';
import {rootReducer} from './rootReducer';
import {TodoActions} from './todoActions';
import {APP_DECLARATIONS} from './app.declarations';

const appStore = createStore(rootReducer);

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    App,
    ...APP_DECLARATIONS
  ],
  providers: [
    { provide: 'AppStore', useValue: appStore },
    TodoActions 
  ],
  bootstrap: [ App ]
})
export class AppModule { }

platformBrowserDynamic().bootstrapModule(AppModule);
import {AddTodo} from './addTodo';
import {TodoList} from './todoList';
import {Todo} from './todo';
import {Filters} from './filters';
import {FilterLink} from './filterLink';
import {VisibleTodosPipe} from './visibleTodosPipe';

export const APP_DECLARATIONS = [
  AddTodo, 
  TodoList,
  Todo,
  Filters, 
  FilterLink, 
  VisibleTodosPipe
];