albatrosary's blog

UI/UXとエンタープライズシステム

Angular2の「5 MIN QUICKSTART」をやったから勢いでTodosもやってみた - AngularJS Advent Calendar 2015

「5 MIN QUICKSTART」の勢いでTodos。このエントリーは「AngularJS Advent Calendar 2015」12月21日の記事です。

作業ディレクトリの作成

適当な名前でディレクトリを作る

mkdir angular2todos && cd $_

モジュールのインストール

npm init -y
npm install angular2 --save
npm install systemjs --save
npm install typescript --save
npm install todomvc-common --save
npm install todomvc-app-css --save
npm install store.js --save
npm install -g live-server

CSSに関してはtodomvc*があるのでとても便利、この辺りあまり関係ないので。store.jsはWeb Storageを利用するためのパッケージ。

必要なファイルを作る

今回はサービスも作る

touch index.html
touch app.ts
mkdir services
touch services/store.ts

型定義ファイル

型定義ファイル管理ツールをインストール

npm install -g tsd

必要な型定義ファイルをインストール

tsd init
tsd install angular2 --save
tsd install node-uuid --save
tsd install storejs --save

index.html

ざっとこんな感じ

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
    <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
    <script src="node_modules/systemjs/dist/system.js"></script>
    <script src="node_modules/typescript/lib/typescript.js"></script>
    <script src="node_modules/store.js/store.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
    <script>
    </script>
  </head>
    <body>
        <todo-app></todo-app>
        <footer class="info">
            <p>Double-click to edit a todo</p>
            <p>
                Created by <a href="http://github.com/samccone">Sam Saccone</a>
                using <a href="http://angular.io">Angular2</a>
            </p>
            <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
        </footer>
        <script>
      System.paths['node-uuid'] = 'node_modules/node-uuid/uuid.js';
      System.paths['services/store'] = 'services/store.ts';
           
      System.config({
        transpiler: 'typescript',
        typescriptOptions: { emitDecoratorMetadata: true }
      });
      System.import('./app.ts');
       </script>
    </body>
</html>

app.ts

こちらも

/// <reference path="typings/tsd.d.ts" />
import {Component, View, bootstrap, NgIf, NgFor} from 'angular2/angular2';
import {TodoStore, Todo} from 'services/store';

const ESC_KEY = 27;
const ENTER_KEY = 13;

@Component({
    selector: 'todo-app',
})
@View({
    directives: [NgIf, NgFor],
    template: `
        <section class="todoapp">
            <header class="header">
                <h1>todos</h1>
                <input class="new-todo" placeholder="What needs to be done?" autofocus="" #newtodo (keyup)="addTodo($event, newtodo)">
            </header>
            <section class="main" *ng-if="todoStore.todos.length > 0">
                <input class="toggle-all" type="checkbox" *ng-if="todoStore.todos.length" #toggleall [checked]="todoStore.allCompleted()" (click)="todoStore.setAllTo(toggleall)">
                <ul class="todo-list">
                    <li *ng-for="#todo of todoStore.todos" [class.completed]="todo.completed" [class.editing]="todo.editing">
                        <div class="view">
                            <input class="toggle" type="checkbox" (click)="toggleCompletion(todo.uid)" [checked]="todo.completed">
                            <label (dblclick)="editTodo(todo)">{{todo.title}}</label>
                            <button class="destroy" (click)="remove(todo.uid)"></button>
                        </div>
                        <input class="edit" *ng-if="todo.editing" [value]="todo.title" #editedtodo (blur)="stopEditing(todo, editedtodo)" (keyup.enter)="updateEditingTodo(editedtodo, todo)" (keyup.escape)="cancelEditingTodo(todo)">
                    </li>
                </ul>
            </section>
            <footer class="footer" *ng-if="todoStore.todos.length > 0">
                <span class="todo-count"><strong>{{todoStore.getRemaining().length}}</strong> {{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left</span>
                <button class="clear-completed" *ng-if="todoStore.getCompleted().length > 0" (click)="removeCompleted()">Clear completed</button>
            </footer>
        </section>`
})
class TodoApp {
    todoStore: TodoStore;
    constructor() {
        this.todoStore = new TodoStore();
    }
    stopEditing(todo: Todo, editedTitle) {
        todo.setTitle(editedTitle.value);
        todo.editing = false;
    }
    cancelEditingTodo(todo: Todo) { todo.editing = false; }
    updateEditingTodo(editedTitle, todo: Todo) {
        editedTitle = editedTitle.value.trim();
        todo.editing = false;

        if (editedTitle.length === 0) {
            return this.todoStore.remove(todo.uid);
        }

        todo.setTitle(editedTitle);
    }
    editTodo(todo: Todo) {
        todo.editing = true;
    }
    removeCompleted() {
        this.todoStore.removeCompleted();
    }
    toggleCompletion(uid: String) {
        this.todoStore.toggleCompletion(uid);
    }
    remove(uid: String){
        this.todoStore.remove(uid);
    }
    addTodo($event, newtodo) {
        if ($event.which === ENTER_KEY && newtodo.value.trim().length) {
            this.todoStore.add(newtodo.value);
            newtodo.value = '';
        }
    }
}

bootstrap(TodoApp);

services/store.ts

/// <reference path="../typings/node-uuid/node-uuid.d.ts" />
/// <reference path="../typings/storejs/storejs.d.ts" />
/// <reference path="../typings/angular2/angular2.d.ts" />

import {Injectable} from 'angular2/angular2';
import * as uuid from 'node-uuid';

export class Todo {
    completed: Boolean;
    editing: Boolean;
    title: String;
    uid: String;
    setTitle(title: String) {
        this.title = title.trim();
    }
    constructor(title: String) {
        this.uid = uuid.v4();
        this.completed = false;
        this.editing = false;
        this.title = title.trim();
    }
}

@Injectable()
export class TodoStore {
    todos: Array<Todo>;
    constructor() {
        let persistedTodos = store.get('angular2-todos') || [];
        // Normalize back into classes
        this.todos = persistedTodos.map( (todo: {title: String, completed: Boolean, uid: String}) => {
            let ret = new Todo(todo.title);
            ret.completed = todo.completed;
            ret.uid = todo.uid;
            return ret;
        });
    }
    _updateStore() {
        store.set('angular2-todos', this.todos);
    }
    get(state: {completed: Boolean}) {
        return this.todos.filter((todo: Todo) => todo.completed === state.completed);
    }
    allCompleted() {
        return this.todos.length === this.getCompleted().length;
    }
    setAllTo(toggler) {
        this.todos.forEach((t: Todo) => t.completed = toggler.checked);
        this._updateStore();
    }
    removeCompleted() {
        this.todos = this.get({completed: false});
    }
    getRemaining() {
        return this.get({completed: false});
    }
    getCompleted() {
        return this.get({completed: true});
    }
    toggleCompletion(uid: String) {
        for (let todo of this.todos) {
            if (todo.uid === uid) {
                todo.completed = !todo.completed;
                break;
            }
        };
        this._updateStore();
    }
    remove(uid: String) {
        for (let todo of this.todos) {
            if (todo.uid === uid) {
                this.todos.splice(this.todos.indexOf(todo), 1);
                break;
            }
        }
        this._updateStore();
    }
    add(title: String) {
        this.todos.push(new Todo(title));
        this._updateStore();
    }
}

実行

実行は

live-server

最後に

ほぼこれ

AngularJS • TodoMVC

作ったソースはこれ。タブとスペースが。。。

github.com