Goalist Developers Blog

はじめてのAngular TODOアプリをつくる③ ルーティング編

はじめてAngular2を触ってみた初心者のメモ書きです。

公式チュートリアルをチラ見しながらTODOアプリを作っています。
今回は第三回のルーティング編です。
前回に引き続き、appコンポーネントをドコドコ分割していきます。

やりたいこと

  • ダッシュボードを作る
  • ダッシュボード、タスク一覧、タスク詳細のビューを切り替える
  • それぞれのビューにURLパスを割り振る
  • TODOアプリとしての体裁を整える

チュートリアルで言うとこのあたりです。

Angular Docs

今回もAngular CLIの恩恵にあずかってまいります。

appコンポーネントを分割する

新しい子コンポーネントとしてTasksComponentを作ります。

ng g component tasks

で、今までappコンポーネントにそのまま書いてたタスク一覧と詳細部分を子側に切り取って貼っちゃいます。

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})

export class AppComponent {
  private title = 'todo app';

}

app.component.html

<h1>{{title}}</h1>
<app-tasks></app-tasks>

だいぶすっきりしました。

ルーティングを追加する

クライアント側でルーティングまでやってしまうんですね~

index.htmlのbase hrefを変更

<base href="/">にする、ということでしたが、
Angular CLIでプロジェクト作ったのでデフォルトでそうなってました。

app.module.tsにRouterModuleをインポート

で、パスと呼び出すコンポーネントを宣言。

app.module.ts

import { RouterModule } from '@angular/router';
・・・
@NgModule({
  ・・・
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      {
        path: 'list',
        component: TasksComponent
      }
    ])
  ],
  ・・・
})

リンクを作る

さっき宣言したパスへのリンクを貼ります。
呼び出されたコンポーネントが<router-outlet></router-outlet>に表示されます。

app.component.html

<h1>{{title}}</h1>
<a routerLink="/list">task list</a>
<router-outlet></router-outlet>

トップページはこうなって

f:id:y-iio:20170411180658p:plain

/listでタスク一覧が表示されるようになりました。

f:id:y-iio:20170411180717p:plain

ダッシュボードを作る

コンポーネント追加

いつものようにng g componentでコンポーネント追加します。

ng g component dashboard

ルーティング追加

先ほど同様、app.module.tsにルーティングを追加します。
初期表示時にリダイレクトさせたいのでリダイレクトルートも追加。

app.module.ts

RouterModule.forRoot([
  {
    path: 'list',
    component: TasksComponent
  },
  {
    path: 'dashboard',
    component: DashboardComponent
  },
  {
    path: '',
    redirectTo: '/dashboard',
    pathMatch: 'full'
  },
])

これで初期表示(/にアクセス)時に/dashboardがリダイレクト表示されるようになりました。

ナビゲーションを作る

app.component.htmlにもリンクを貼り、ナビゲーション完成です。

app.component.html

<h1>{{title}}</h1>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/list">Task List</a>
</nav>
<router-outlet></router-outlet>

ダッシュボードの中身を作る

チュートリアルだとダッシュボードに表示されるHeroはid若い順に5人という謎な感じですから、
このTODOアプリでは重要なタスクだけ表示してみようとします。

Taskクラスに重要度を追加して、
ついでにTODOアプリとしての体裁のために完了フラグも追加して、

task.ts

export class Task {
  public id: number;
  public name: string;
  public importance: string;
  public isDone: boolean = false;
}

モックデータにも重要度、完了フラグを付けます。

mock-tasks.ts

import { Task } from './task';

export const TASKS: Task[] = [
  { id: 11, name: '企画ロードマップ作成', importance: 'high', isDone: false },
  { id: 12, name: '山田さんにメール返信', importance: 'high', isDone: false },
  { id: 13, name: 'Angular2キャッチアップ', importance: 'mid', isDone: false },
  { id: 14, name: 'ブログ更新', importance: 'low', isDone: false },
  { id: 15, name: '新卒技術研修', importance: 'low', isDone: false }
];

タスク詳細画面で重要度を編集できるようにして…
(もっと良いやり方ありそうですがとりあえずこれで許して)

task-detail.component.html

<div>
  <label>importance: </label>
  <select [(ngModel)]="task.importance">
    <!-- options = ['low', 'mid', 'high'] -->
    <option *ngFor="let option of options" [value]="option">{{option}}</option>
  </select>
</div>

ダッシュボードは重要度の高いタスクのみフィルタして表示します。

dashboard.component.ts

export class DashboardComponent implements OnInit {
  private tasks: Task[];

  constructor(private taskService: TaskService) { }

  private getTasks(): void {
    this.taskService.getTasks().then(retTasks => {
      this.tasks = retTasks.filter(function(retTask) {
        return retTask.importance === 'high';
      });
    });
  }

  ngOnInit() {
    this.getTasks();
  }
}

dashboard.component.html

<h3>Important Tasks</h3>
<div class="grid grid-pad">
  <a *ngFor="let task of tasks" [routerLink]="['/detail', task.id]" class="col-1-4">
    <div class="module task">
      <h4>{{task.name}}</h4>
    </div>
  </a>
</div>

CSSもチュートリアルからパクってきて適当に入れました。

f:id:y-iio:20170411180802p:plain

それらしくなってきました。

TODOアプリっぽくする

TODOアプリとしての体裁のために完了フラグをねじこんだのですが、
とりあえずtask-detailから変更できるようにしてみました。

task-detail.component.html

<h2>
 {{task.name}}
  <span *ngIf="!task.isDone">details</span>
  <span *ngIf="task.isDone">is DONE!!!</span>
</h2>
・・・
<button class="btn-primary" (click)="done()">done!!!</button>

task-detail.component.ts

public done(): void {
  this.task.isDone = true;
}

サービスで完了したものはフィルタします。

task.service.ts

public getTasks(): Promise<Task[]> {
  return Promise.resolve(TASKS.filter(function(task) {
     return !task.isDone;
  }));
}

これで完了ボタンを押すと

f:id:y-iio:20170424185755p:plain

リストで非表示になります。

f:id:y-iio:20170424185817p:plain

タスク詳細ビューもルーティング

ルーティング追加

先ほど同様、app.module.tsにルーティングを追加します。
:idでパラメータ送れるみたいな?

app.module.ts

{
  path: 'detail/:id',
  component: TaskDetailComponent
}

サービスにメソッド追加

ID指定でタスクひとつを返却するメソッドを追加します。

task.service.ts

public getTask(id: number): Promise<Task> {
  return this.getTasks().then(tasks => tasks.find(task => task.id === id));
}

タスク詳細でパラメータ取得できるようにする

どんどこimportしていきます。

task-detail.component.html

import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; // 追加
import { Location } from '@angular/common'; // 追加
import 'rxjs/add/operator/switchMap'; // 追加

import { Task } from '../task';
import { TaskService } from '../task.service'; // 追加

const OPTIONS: string[] = ['low', 'mid', 'high'];

@Component({
  selector: 'app-task-detail',
  templateUrl: './task-detail.component.html',
  styleUrls: ['./task-detail.component.scss']
})
export class TaskDetailComponent implements OnInit {
  @Input() task: Task;
  options = OPTIONS;

  // コンストラクタで呼び出し
  constructor(
    private taskService: TaskService,
    private route: ActivatedRoute,
    private location: Location
  ) { }

  // コンポーネント初期表示時に実行
  ngOnInit(): void {
    // パラメータの値を取得して、タスク詳細を呼び出す
    this.route.params
      .switchMap((params: Params) => this.taskService.getTask(+params['id']))
      .subscribe(task => this.task = task);
  }

}

ちょっとこの部分

this.route.params
  .switchMap((params: Params) => this.taskService.getTask(+params['id']))
  .subscribe(task => this.task = task);

のわかんなさですが、非同期でgetTaskしてきて?
subscribeのときに結果を代入してる?

ブラウザバック実装

詳細ビューにはダッシュボードか一覧に戻れるように戻るボタンを付けてあげます。

メソッドは

public goBack(): void {
  this.location.back();
}

ダッシュボードからも一覧からも詳細が見られるようになりました~

f:id:y-iio:20170411180930p:plain

ルーターを別モジュールにまとめる

いつものng generateでモジュールも作れます。

ng g module app-routing

AppRoutingモジュールの中身はapp.module.tsから取ってきてこんなかんじに

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent } from '../dashboard/dashboard.component';
import { TaskDetailComponent } from '../task-detail/task-detail.component';
import { TasksComponent } from '../tasks/tasks.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'list', component: TasksComponent },
  { path: 'detail/:id', component: TaskDetailComponent },
];

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forRoot(routes)
  ],
  exports: [ RouterModule ],
  declarations: []
})
export class AppRoutingModule { }

これをAppModule側にインポートして、
importsにAppRoutingModuleを追加してあげれば分離完了です。
大変な道のりでした。

後は全体的にCSSをば調整して…こんな感じになりました。

f:id:y-iio:20170411180955p:plain

f:id:y-iio:20170411181045p:plain

f:id:y-iio:20170411181006p:plain

感想

ちょっと本当に今回は長くて大変でした。
チュートリアルに沿っているだけで、モジュール?コンポーネント?ハハハ
まあいつか分かればいいです。

次回感動の最終回、TODOアプリ④ HTTP編を刮目して見よ!
GW明けに投稿予定です。

はじめてのAngular TODOアプリをつくる② コンポーネントとサービス編

はじめてAngular2を触ってみた初心者のメモ書きです。

前回の投稿から、公式チュートリアルをチラ見しながらTODOアプリを作っています。
今回は、前回作ったappコンポーネントをサクサクと分割していきたいと思います。
チュートリアルで言うとこのあたりです。

Angular Docs

今回もAngular CLIの恩恵を享受していきます。

目次

コンポーネントを分割する

コンポーネントを作成(ng generate component)

ファイル名はケバブケースでコンポーネント作成します。(最初キャメルにしたらAngular CLIに直された)

ng g component task-detail

作成したコンポーネントをインポート

前回同様Taskクラスをインポートします。import { Task } from './task';
app.component.ts、task-detail.component.tsどちらにもインポート文が必要です。

親コンポーネントのプロパティをバインド

また子コンポーネントのプロパティに@Input()アノテーションを付けることで、
親コンポーネントの値がバインドされるようになります。
よくわかんないけど親側での変更が反映されるんだな~と思っておきます。

task-detail.component.ts

//ここでInputアノテーションが使えるようにInputをインポートしてます
import { Component, OnInit, Input } from '@angular/core';

import { Task } from './task';

@Component({
  selector: 'app-task-detail',
  templateUrl: './task-detail.component.html',
  styleUrls: ['./task-detail.component.scss']
})
export class TaskDetailComponent implements OnInit {
  @Input() task: Task;

  constructor() { }

  ngOnInit() {
  }

}

ディレクティブを置き換えてコンポーネント表示

task-detail.component.html

<div *ngIf="task">
  <h2>{{task.name}} details!</h2>
  <div><label>id: </label>{{task.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="task.name" placeholder="name"/>
  </div>
</div>

今まで↑のディレクティブが書いてあった部分を今作ったコンポーネントに置き換えます。
selectedTaskをtaskというプロパティ名で子コンポーネントに送ってバインドしています。

app.component.html

<app-task-detail [task]="selectedTask"></app-task-detail>

ヌゥ…なんでエラー

ERROR in C:/angularWork/my-new-app/src/app/task-detail/task-detail.component.ts (3,22): Cannot find module './task'.)

ERROR in ./src/app/task-detail/task-detail.component.ts
Module not found: Error: Can't resolve './task' in 'C:\angularWork\my-new-app\src\app\task-detail'
 @ ./src/app/task-detail/task-detail.component.ts 11:0-30
 @ ./src/app/app.module.ts
 @ ./src/main.ts
 @ multi webpack-dev-server/client?http://localhost:4200/ ./src/main.ts

appコンポーネントにも同じインポート文書いてるのにな?と思ったら
task-detail.component.tsはapp/task-detail/下にあったのでした。
import { Task } from './task';じゃなくて
import { Task } from '../task';でした。
変なところで時間使ってしまった。

これでコンポーネント分割できました~見た目は全く変わってませんが。

f:id:y-iio:20170410164651p:plain

サービスを使う

気を取り直して、サービスを作っていきます。
チュートリアルで言うとこのあたりです。

Angular Docs

サービスを作る(ng generate service)

新しいコマンドですが、いつものようにng g

ng g service task
  installing service
    create src\app\task.service.spec.ts
    create src\app\task.service.ts
    WARNING Service is generated but not provided, it must be provided to be used

not provided?ということは何かやらないと使えないのか…

app下にtask.service.tsが作られてました。
中身はこう。

import { Injectable } from '@angular/core';

@Injectable()
export class TaskService {

  constructor() { }

}

@Injectable()アノテーションは最初からつけておいてくれるんですね。

モックデータを移動する

今までapp.component.tsで宣言していた配列を別ファイルに分離させます。
Taskクラスも忘れずにインポート。

src/app/mock-tasks.ts

import { Task } from './task';

export const TASKS: Task[] = [
  { id: 11, name: '企画ロードマップ作成' },
  { id: 12, name: '山田さんにメール返信' },
  { id: 13, name: 'Angular2キャッチアップ' },
  { id: 14, name: 'ブログ更新' },
  { id: 15, name: '新卒技術研修' }
];

親コンポーネントのプロパティであるところのtasksは初期化しないように変更します。

app.component.ts

export class AppComponent {
  private title = 'todo app';
  private tasks: Task[];// ここです
  private selectedTask: Task;
}

task.service.tsでモックデータを受け取って、返却するようにします。

import { Injectable } from '@angular/core';

import { Task } from './task';
import { TASKS } from './mock-tasks';

@Injectable()
export class TaskService {
  public getTasks(): Task[] {
    return TASKS;
  }

  constructor() { }

}

TaskServiceをインポートする

作ったサービスをAppComponentで使えるようにしていきます。
いつものようにインポート文を加えて

import { TaskService } from './task.service';

コンストラクタでサービスインスタンスを取得します。newして呼び出してはいけないそうです。

export class AppComponent {
  private title = 'todo app';
  private tasks: Task[];
  private selectedTask: Task;

  constructor(private taskService: TaskService) {
  }

  public onSelect(task: Task): void {
    this.selectedTask = task;
  }
}

で保存したらエラー

f:id:y-iio:20170410202740p:plain

そういえばさっきnot providedって言われてました。
app.module.tsでprovidersを宣言しないといけないようです。

app.module.ts

@NgModule({
 ・・・
  providers: [TaskService], 
 ・・・
})

これでエラーが消えました。一安心。
サービスのgetTasks()を宣言しなおします。

app.component.ts

private getTasks(): void {
  this.tasks = this.taskService.getTasks();
}

ngOnInitというメソッドが、コンポーネントの生成後に呼ばれるそうなので、
ここでさっき作ったgetTasks()を呼んでもらいます。

app.component.ts

import { Component, OnInit } from '@angular/core'; // OnInitもインポート
・・・
export class AppComponent implements OnInit { // implementsでOnInitを使えるようにして
  private title = 'todo app';
  private tasks: Task[];
  private selectedTask: Task;

  constructor(private taskService: TaskService) { }

  private getTasks(): void {
    this.taskService.getTasks().then(tasks => this.tasks = tasks);
  }

  // ここで使う
  ngOnInit() {
    this.getTasks();
  }
}

これでモックデータを呼んでくることができるようになりました~
見た目は全く変わってませんが。

f:id:y-iio:20170410164440p:plain

非同期でサービスを使う

promise使って非同期でモックデータを持ってきてみます。

TaskServiceのgetTasks()メソッドでTasksを返していたところを、
Promise<Tasks
>を返すように変更します。

task.service.ts

public getTasks(): Promise<Task[]> {
  return Promise.resolve(TASKS);
}

tasks.component.ts

private getTasks(): void {
  this.taskService.getTasks().then(tasks => this.tasks = tasks);
}

アロー関数、初めて使いました。
thisを別名で宣言しなくて良いんですね。

これで非同期でデータを取ってくることができるようになりました~
見た目は全く変わってませんが。

f:id:y-iio:20170410164440p:plain

ソースコードまとめ

感想

なんだか大変だったわりに見た目は全く変わっていません。
何にもやってないみたいじゃないか、いやいや学びはありました。
次回でもう少しは体裁を整えられるのか、TODOアプリを作る③ ルーティング編、乞うご期待!

初心者向け CocoaPods の使い方 [導入編]

こんちは。渡部です。

今回はCocoaPodsについて書きます。
ライブラリを使う際にGitHubからダウンロードして
プロジェクトにファイルを追加してる絶滅寸前の方達に参考にしていただければと思います。

f:id:watabe1028:20170410111722p:plain

CocoaPodsってなによ?

iOSやMac向けアプリで使用するライブラリを管理してくれるものです。
GitHubからzipをダウンロードして、プロジェクトに追加するのもありだけど
更新があった際に面倒っす。
CocoaPodsならコマンドで一発で更新できる。
つまり管理が楽

どうやって使うのよ?

インストールします。
Rubyが入ってること前提ですがMacならデフォルトで入ってるはずです。
Windowsユーザは帰れ。

1.ruby gemを最新にする

ターミナルを起動して以下を入力していきます。

sudo gem update —system
2.インストール
sudo gem install cocoapods
3.セットアップ
pod setup

これで使う準備おけ!

CocoaPodsを使う

1.プロジェクトに移動する

今回はデスクトップのTestAppに移動。

cd Desktop/TestApp/
2.Podfileを作成して設定を追加する

ここのコマンド入力前にXcodeを閉じてください。
エラーになりやす。
これでPodsファイルを作成します。

pod init

ファイルの中身をみてみます。

vi Podfile

こんな感じで表示されるはずです。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'TestApp' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for TestApp
end

viでファイルが開くので「i」を押して編集モードにします。
「# Pods for TestApp」の下に使いたいライブラリの名前を
「pod ‘XLPagerTabStrip’」のように入力します。

  # Pods for TestApp
  pod ‘XLPagerTabStrip’
end

入力が終わったら「esc」で編集モードを終わらせて
「:wq」で保存、終了。

ここを解説すると
環境はiOS9以上ですよ。
フレームワーク使うよ。
このプロジェクトで使うよ。
このライブラリ使うよ。
終わり

って感じです。
複数のライブラリを使う際は

pod ‘XLPagerTabStrip’
pod 'JSQMessagesViewController'
pod 'SVProgressHUD'
end

このように続けて書きます。

3.ライブラリのインストール

指定したライブラリをインストールします。

pod install

で、先ほど設定したXLPagerTabStripがインストールされます。
さらにプロジェクトをFinderで開くと「.workspace」が作られているはずです。
podsを使う際は「.xcodeproj」ではなく「.xcworkspace」から開いて開発します。

4.更新

念のためアップデートします。

pod update

ライブラリの更新がなければ普通に終わります。
基本的にライブラリの更新があった場合はpod updateすれば
全てのライブラリの更新が一発で終わります。

まとめ

モバイルのフロントエンドをやってる人たちの中には
ターミナルでコマンド打つのに苦手意識を持ってる人(俺)が多いと思います。
でも一回設定すればあとは限られたコマンドしか打たないので
管理コストを鑑みてもCocoaPodsの導入はプラスになるはずです。

はじめてのAngular TODOアプリをつくる① リスト表示編

はじめてAngular2を触ってみた初心者のメモ書きです。

今回からは、公式チュートリアルをチラ見しながらTODOアプリを作っていきたいと思います。

https://angular.io/docs/ts/latest/tutorial/angular.io

今回もAngular CLIの恩恵にあずかってまいります。
Windowsでの環境構築編はこちら

developers.goalist.co.jp

目次

準備

プロジェクトを生成

ng new todo-app

開発用サーバを起動

ng s

これで保存の度に自動ビルドしてくれます。

http://localhost:4200/をブラウザ上で確認
f:id:y-iio:20170410163753p:plain

ちゃんと動いてますね。これで準備完了です。

タスクを一つ表示する

公式チュートリアルのHero Editorを見ながらやっていきます。
内容としては

Angular Docs

のところです。

Taskクラスの作成

ng g class task

これでapp下にtask.tsが作成されました。
とりあえずIDとタスク名を持たせます。

task.ts

export class Task {
  id: number;
  name: string;
}

Taskクラスをインポート

appコンポーネントにimport { Task } from './Task';を追加して、Taskクラスを使えるようにします。
適当に初期表示も決め打ちします。

app.component.ts

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

import { Task } from './Task';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'todo app';
  task: Task = {
    id: 1,
    name: '田中さんにメール'
  };
}

チュートリアルのHero Editorほぼそのままのhtmlです。

app.component.html

<h1>{{title}}</h1>
<h2>{{task.name}} の詳細</h2>
<div><label>id: </label>{{task.id}}</div>
<div><label>name: </label>{{task.name}}</div>
<div>
  <label>name: </label>
  <input [(ngModel)]="task.name" placeholder="name">
</div>

表示ドン

f:id:y-iio:20170410164136p:plain

双方向バインディング[(ngModel)]もオッケーです。

f:id:y-iio:20170410164145p:plain

タスクリストを表示する

内容としては

Angular Docs

のところです。

リスト表示(ngFor)

とりあえずリスト内容を宣言
このあたりで良いのかな

app.component.ts

export class AppComponent {
  title = 'todo app';
  task: Task = {
    id: 1,
    name: '田中さんにメール'
  };
  tasks = TASKS;
}
const TASKS: Task[] = [
  { id: 11, name: '企画ロードマップ作成' },
  { id: 12, name: '山田さんにメール返信' },
  { id: 13, name: 'Angular2キャッチアップ' },
  { id: 14, name: 'ブログ更新' },
  { id: 15, name: '新卒技術研修' }
];

ngForを使ってリスト表示します。
テンプレート構文の使い方は前回のブログで!

app.component.html

<ul class="tasks">
  <li *ngFor="let task of tasks">
    <span class="badge">{{task.id}}</span> {{task.name}}
  </li>
</ul>

こんな感じでリスト表示できました。

f:id:y-iio:20170410164420p:plain

CSSを追加

チュートリアルにあるCSSの「heroes」を「tasks」に変えて使いたいので、Sassでいきます。
app.component.scssを作って呼び出します。コンパイルもしてくれるっぽい(すごい!)

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})

一気にそれっぽくなりました!

f:id:y-iio:20170410164440p:plain

タスクを選択できるようにする

クリックイベントを追加

前回さらった(click)を使ってイベントを呼び出します。

app.component.html

<li *ngFor="let task of tasks" (click)="onSelect(task)">

スクリプト側にハンドラを追加します。

app.component.ts

selectedTask: Task;

onSelect(task: Task): void {
  this.selectedTask = task;
}

選ばれしタスクを表示(ngIf)

前回やったngIfを使います。
selectedTaskに値を入ったらば*ngIf="selectedTask"がtrueになるので、以下の要素が表示されるようになります。

app.component.html

<div *ngIf="selectedTask">
  <h2>{{selectedTask.name}} details!</h2>
  <div><label>id: </label>{{selectedTask.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedTask.name" placeholder="name"/>
  </div>
</div>

タスクをクリックしたら…

f:id:y-iio:20170410164620p:plain

詳細が表示されるようになりました。

f:id:y-iio:20170410164629p:plain

選択中のタスクに色を付ける([class.クラス名])

これは前回はしょってやらなかったのですが、
[class.クラス名]="式"でCSSクラスの追加/削除ができるようです。

app.component.html

<li *ngFor="let task of tasks" [class.selected]="task === selectedTask" (click)="onSelect(task)">

選択されている("task === selectedTask"がtrue)ときはselectedクラスをaddしてくれます。 "task === selectedTask"がfalseならremoveしてくれます。(便利!)

f:id:y-iio:20170410164651p:plain

ソースコードまとめ

感想

ngFor, ngnIf, CSSクラスの追加/削除を学びました。
今後もチュートリアルに沿ってどんどん進めていく所存です。

ちんたらやってるうちに業務で本格的に使うことになってしまいました…頑張ります…

HRogチャートの開発フロー(バックエンド編)

f:id:suzutt:20170217000629p:plain

こんにちは2ヶ月ぶりの執筆になります。鈴木です。

新卒メンバーが入社し、CTO入倉と2年目エンジニアの飯尾が技術研修をしていまして、 毎日めきめきスキルアップしているようです。

のちのち自分も開発フロー研修をする予定です。 Qiitaをみても意外とIDEのプラグインを含めて使用方法がまとまっていなかったのでブログに書いてみました。

検証済みバージョン

以下のバージョンで検証済みです。

  • OS
    • OS X El Capitan(10.11.5)
  • Eclipse
    • Luna (4.4.2)
  • Java
    • 1.8.0_25
  • Gradle
    • 3.3
  • git
    • 2.4.2

Eclipse プラグインのバージョンです。

  • EGit
    • 3.4.2
  • Buildship: Eclipse Plug-ins for Gradle
    • 1.0.20

開発ツールのインストール

事前に以下のツールをインストールしてください。

  • Eclipse
  • 上記のEclipseプラグイン
  • git
  • Gradle

Gradleは主にプロジェクトの依存ライブラリの解決に使います。

プロジェクトの設定

Github上のリモートリポジトリのクローン

コマンドラインより、Eclipseのワークスペースに設定したディレクトリ上で以下のコマンドを実行します。

git clone https://github.com/xxxxxx/yyyyyy

Eclipseプロジェクト化

IDEの設定はEclipseの設定ファイル(.project, .classpath, .settings/)はコミットしたくないので、 clone後にはプロジェクト直下でコマンドを実行することで、これらのファイルを作成します。

gradle eclipse

賛否両論ありますが、特にクラスパスの設定がGradleの設定ファイル(build.gradle)とEclipseの設定ファイル(.classpath)でダブルメンテになってしまうのが嫌なのでこのようにしています。

Eclipseへのインポート

Eclipseを起動し、

File > Import > General > Exsiting Projects into Workspace

より、該当のプロジェクトをワークスペースにインポートしてください。

もしプロジェクトが認識されないのであれば、Eclipseプロジェクト化されていないので、 「Eclipseプロジェクト化」の手順が実施されているか再度見直してください。

Gradle Natureを追加

EclipseにGradleプロジェクトだと認識させるために以下の手順を実行します。

プロジェクトを右クリック > Configure > Add Gradle Nature

Gradleプロジェクトと認識されないと、build.gradleを元に外部ライブラリをダンロードしたり、それをクラスパスに追加したりしてくれません。

なぜ、そもそもGradleプラグインを使ってプロジェクトを作成しないのかと思ってる人がいましたら、以下のQiita記事がとてもわかりやすかったのでそちらを。。

qiita.com

Gradleプラグインを使ってプロジェクトを作成すると、コンパイルバージョンが1.5になってしまうようです。

Gradle プロジェクトをリフレッシュ

プロジェクトを右クリック > Gradle > Refresh Gradle Project

により、build.gradleの設定にしたがって、外部ライブラリがダウンロードされ、 クラスパスに追加されます。

コンパイルが通ることを確認する

この時点でコンパイルが通るはずです。もしエラーが出ているようであれば手順実施ミスか、そもそものプロジェクトがイケてないので、迷わずプロジェクトメンバーに相談を。

Githubフローに沿った開発

無事コンパイルが通れば、プロジェクトの設定は完了です。 ここからGithubフローに沿って、開発していきます。

流れとしては開発対象の機能をきめて、専用のトピックブランチを作ります。 開発のきりのいいタイミングで add, commit を繰り返し、こまめにGithub上のリモートリポジトリに push します。 フィードバックが欲しい場合や、変更が完了した場合には push 後にブラウザ上からプルリクエストをします。

以下のGithubフローについては以下の日本語訳が参考になります。

GitHub Flow (Japanese translation) · GitHub

トピックブランチの作成

機能ごとにトピックブランチを作成します。

右クリック > Team > Switch to > New branch

名前は開発内容がわかるような名前をつけます

(例)delete-freeplan-message

開発しながら、変更をgitで管理します。

コミット対象のファイルを指定

コミット対象のファイルを指定します。

プロジェクトを右クリック > Team > Add to Index

コミットする

プロジェクトを右クリック > Team > Commit

必ずコメントを入力します。チケット管理している場合はチケット番号もコメントに含めます。後で他の人が変更を追いやすくすることが目的です。

Github上のリモートリポジトリに反映する

プロジェクトを右クリック > Team > Push Branch

変更が完了したら、Github上のリモートリポジトリに変更を反映します。

プルリクエストする

開発が完了した場合や、フィードバックが欲しい場合に、プルリクエストを行います。 ブラウザ上から以下の記事を参考に実行します。

qiita.com

Eclipse上から出来るといいんですけど、そういうプラグインはないみたいですね。

ソースコードレビューを経て、マージ

プルクエストをされた内容をソースコードレビューし、問題がなければマージされます。 指摘があれば修正後、再度プルリクエストを行います。

そして、次の機能開発へ…

無事マージされたら、次の開発へ。
新しくトピックブランチを作成し、繰り返し開発していきます。

まとめ

本当はユニットテストについても作りたかったのですが、書けるほど詳しくないので断念しました。多分勝手に追記するか、次の記事で書きます。 次回投稿時はユニットテストの話か「HRogチャートの開発フロー(フロントエンド編) 」を書こうかなと思っています。

それでは。

はじめてのAngular やさしいテンプレートシンタックス編

はじめてAngularを触ってみた初心者のメモ書きです。

前回のブログではAngular CLIを使って環境構築を行いました。

developers.goalist.co.jp

今回はAngularのテンプレート構文を使ったデータバインドを試していきます。

目次

準備

前回の復習もかねてコンポーネントを追加してみます。 template-testという名前で作ってみました。

$ng g component template-test
installing component
  create src\app\template-test\template-test.component.css
  create src\app\template-test\template-test.component.html
  create src\app\template-test\template-test.component.spec.ts
  create src\app\template-test\template-test.component.ts
  update src\app\app.module.ts

前回同様app.component.htmlに作ったコンポーネントを追加します。

<h1>{{title}}</h1>
<hr>
<app-template-test></app-template-test>

これで準備完了です。 使いそうなテンプレート構文を一通りさらっていきたいと思います。

片方向データバインド

コンポーネントクラスで宣言した文字列を画面側に挿入します。
app.component.htmlの{{title}}部分と同様です。

template-test.component.tsのTemplateTestComponentクラスで式を宣言して、
template-test.component.htmlの{{式}}で出力します。

template-test.component.ts

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

@Component({
  selector: 'app-template-test',
  templateUrl: './template-test.component.html',
  styleUrls: ['./template-test.component.css']
})

export class TemplateTestComponent implements OnInit {
  value = 'goalist';
}

template-test.component.html

<p>
  {{value}}
</p>

ビルドすると画面はこんな感じに

f:id:y-iio:20170327174809p:plain

{{}}の中ではJavaScript式が使えます。
template-test.component.html

export class TemplateTestComponent implements OnInit {
  number = 10;
  ok = true;
  str = 'abcdefg';
}

template-test.component.html

<p>
  {{number * 5}}<br>
  {{ok ? 'YES' : 'NO'}}<br>
  {{str.split('').reverse().join('')}}<br>
</p>

結果はこんな感じ

f:id:y-iio:20170327174831p:plain

関数を呼び出したりもできます。
TemplateTestComponentクラスで関数を宣言して

export class TemplateTestComponent implements OnInit {
  getHoge(): string {
    return 'hoge';
  }
}

HTML側から呼び出し

<p>
  {{getHoge()}}
</p>

結果はこんな感じ

f:id:y-iio:20170327174859p:plain

双方向データバインド

画面側で入力した値と、コンポーネントクラスの変数の値が同期します。

export class TemplateTestComponent implements OnInit {
  inputText = "初期値";
}
<input [(ngModel)]="inputText">
<p>
  {{inputText}}
</p>

コンポーネントクラスで変数宣言するときに初期値を指定しておくことができるようです。
ちなみに変数宣言しなくてもエラーになりませんでした。なぜかしらん。

f:id:y-iio:20170327174918p:plain

リスト表示(ngFor)

for文風に使えます。
配列から要素を取り出して、ngForを書いたHTML要素を繰り返し出力します。
Vue.jsでいうところのv-forとほぼ同じです。

ary = ['りんご', 'ごりら', 'らっぱ']のとき

<div *ngFor="let item of ary">
  {{item}}
</div>

ビルド後はこうなります。

<div>りんご</div>
<div>ごりら</div>
<div>らっぱ</div>

let i=indexのようにインデックス変数を宣言して使うこともできます。

<div *ngFor="let item of ary; let i=index">
  {{i+1}}: {{item}}
</div>

f:id:y-iio:20170327184824p:plain

HTML要素の表示/非表示(ngIf)

if文っぽく使えます。
式の値がtrueのときにngIfを書いたHTML要素を出力します。

isVisible = trueのとき

<div *ngIf="isVisible">このdivは出力される</div>
<div *ngIf="!isVisible">このdivは出力されない</div>

Vue.jsのv-ifと同じで要素自体が無くなります。
v-showみたいにstyle:block/noneみたいなやつはないんでしょうか。

HTML要素の表示切り替え(ngSwitch)

switch文っぽく使えます。
ngSwitchを書いたHTML要素の子要素から、条件に一致するもののみを出力します。

<div [ngSwitch]="condition">
  <div *ngSwitchCase="'case1'">condition = 'case1'のとき出力される</div>
  <div *ngSwitchCase="'case2'">condition = 'case2'のとき出力される</div>
  <div *ngSwitchDefault>どの条件にも一致しないとき出力される</div>
</div>

HTML要素のプロパティ指定

[プロパティ名]="式"で指定したプロパティに値を代入できます。

isDisabled = trueのとき

<button [disabled]="isDisabled">このボタン要素にdisabledが効く</button>

こんな風に使えそうです。

<input id="agreement" type="checkbox" [(ngModel)]="isAgreed">
<label for ="agreement">同意する</label>
<button [disabled]="!isAgreed">送信</button>
isAgreed = false;

f:id:y-iio:20170327184229p:plain

イベント処理

(イベント名)="式"で式を呼び出します。

<button (click)="alert()">アラート表示</button>
alert(): void {
  window.alert('you clicked');
}

f:id:y-iio:20170327185056p:plain

ソースコードまとめ

今回使ったソースはこんな感じになりました。

感想

このブログを書いている間にアップデートがあってAngular2が4になってしまいました。
めげずに頑張ります。

はじめてのAngular Windowsで環境構築~とりあえず触ってみよう編

Windowsで環境構築~とりあえず触ってみよう編

このブログを書いた人

  • プログラミングを始めて1年弱
  • TypeScriptはこれから勉強します
  • JSフレームワークだとVue.jsなら触ったことある
    くらいの初心者

今のところ環境

  • node: 6.3.0
  • os: win32 x64

Angular CLIを入れてみる

開発環境構築にはAngular CLIというツールが便利らしいのでインストール

npm install -g @angular/cli

バージョンは1.0.0-beta.28.3 でした

ヌッ…黄色い字の警告文…

As a forewarning, we are moving the CLI npm package to "@angular/cli" with the next release,
which will only support Node 6.9 and greater. This package will be officially deprecated
shortly after.

node公式を見たら最新安定板はv6.10.0だったのでバージョンを変える

インストール済みのやつを確認

nodist
   (x64)
   4.4.7
 > 6.3.0  (global: 6.3.0)

使いたいバージョンをインストールする

nodist + v6.10.0

バージョンを切り替える

nodist 6.10.0

これでよし

nodist
   (x64)
   4.4.7
   6.3.0
 > 6.10.0  (global: 6.10.0)

プロジェクトを生成してみよう

適当なディレクトリ下で

ng new my-new-app

ちょっと待ちましたが Project 'my-new-app' successfully created. ですって

さっそくサーバー起動

cd my-new-app
ng serve

webpack: Compiled successfully.が出たらhttp://localhost:4200/をブラウザで見てみる

f:id:y-iio:20170316193815p:plain
動いてる~オッホホ!

エディタで中身を見てみよう

Angular CLIが自動でこんなかんじのディレクトリ構成を作ってくれたので f:id:y-iio:20170316193849p:plain

src以下を見ていきます。

index.html

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>MyNewApp</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading...</app-root>
</body>
</html>

<app-root></app-root>のところにたぶんこの部分が入るのか?
f:id:y-iio:20170316193923p:plain

中身をチェックだ

app.component.html

<h1>
  {{title}}
</h1>

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
}

app.component.tsのここの部分でコンポーネント定義されてて

@Component({
  selector: 'app-root', // このセレクタで呼び出す
  templateUrl: './app.component.html', // コンポーネントの中身のhtml参照先のパス
  styleUrls: ['./app.component.css'] // コンポーネントの中身のcss参照先のパス
})

ここがデータバインド部分かしら

export class AppComponent {
  title = 'app works!';
}

titleを変えてみよう

title = 'hello world!';

保存したらng serveのビルド処理が走る!
LiveReloadでブラウザが自動更新される!
f:id:y-iio:20170316194239p:plain
変わってる~!

新しいコンポーネントを作ってみよう

greetingという名前のコンポーネントを作ってみます。

ng generate component greeting
 installing component
  create src\app\greeting\greeting.component.css
  create src\app\greeting\greeting.component.html
  create src\app\greeting\greeting.component.spec.ts
  create src\app\greeting\greeting.component.ts
  update src\app\app.module.ts

(なんかいろいろ作ってくれた…)

app/greetingに中身の一式を
f:id:y-iio:20170316194534p:plain

app.module.tsはこのあたりを書き加えてくれたようです。
f:id:y-iio:20170316194546p:plain

これでimportと宣言されるんで呼び出せるようになるということかしら。

greeting.component.tsを見たところ、
greetingコンポーネントのセレクタ名はapp-greetingになるらしい。
f:id:y-iio:20170316194642p:plain

というわけでapp.component.htmlに以下のように書き加えます。

<h1>
  {{title}}
</h1>
<app-greeting></app-greeting>

greeting.component.htmlを好きに変更して…

<p>
  welcome to my app
</p>

ドン
f:id:y-iio:20170316194707p:plain
コンポーネント足せた~!

まとめ

というわけではじめてのAngular2、とりあえず触ってみることができました。
Angular CLIのおかげで何やら難しげなimportなんかの整理やビルドも楽々です。
最終的には公式チュートリアルを参考にしつつ、TODOアプリ的なものを作ってみたいと思っています。
次回はやさしいテンプレート構文編です。
乞うご期待!