<!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
];