<!DOCTYPE html>
<html>
<head>
<title>angular2 playground</title>
<link rel="stylesheet" href="style.css" />
<script src="https://code.angularjs.org/2.0.0-beta.15/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="config.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.15/Rx.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.15/angular2.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.15/http.min.js"></script>
<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'
}
}
});
//main entry point
import {bootstrap} from 'angular2/platform/browser';
import {provide} from 'angular2/core';
import {App} from './app';
import {createStore} from 'redux';
import {rootReducer} from './rootReducer';
import {TodoActions} from './todoActions';
const appStore = createStore(rootReducer);
bootstrap(App, [
provide('AppStore', { useValue: appStore }),
TodoActions
])
.catch(err => console.error(err));
//our root app component
import {Component} from 'angular2/core'
import {AddTodo} from './addTodo';
import {TodoList} from './todoList';
import {Filters} from './filters';
@Component({
selector: 'root',
template:
`<div>
<add-todo></add-todo>
<todo-list></todo-list>
<filters></filters>
</div>`,
directives: [AddTodo, TodoList, Filters]
})
export class App { }
import {Component, Inject} from 'angular2/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 'angular2/core';
import {Todo} from './todo';
import {VisibleTodosPipe} from './visibleTodosPipe';
@Component({
selector: 'todo-list',
template: `
<ul>
<todo
*ngFor="#todo of todos | visibleTodos:currentFilter"
[completed]="todo.completed"
[id]="todo.id"
>{{todo.text}}</todo>
</ul>
`,
directives: [Todo],
pipes: [VisibleTodosPipe]
})
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 'angular2/core'
import {FilterLink} from './filterLink';
@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>',
directives: [FilterLink]
})
export class Filters { }
import {Component, ContentChildren, Inject, ChangeDetectionStrategy} from 'angular2/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 'angular2/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 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Pipe, PipeTransform} from 'angular2/core';
@Pipe({
name: 'visibleTodos'
})
export class VisibleTodosPipe implements PipeTransform {
transform(todos, args){
if (isBlank(args) || args.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, args[0]);
}
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;
}
};
}