• 35
    いいね
  • 0
    コメント

@armorik83です。今回はangular-cliについてまとめます。

angular-cli

Prototype of a CLI for Angular 2 applications based on the ember-cli project.

angular-cliはAngularの2.x以上(本稿ではAngularと記述する)を用いたアプリケーションの開発を補助するCLIツールである。

インストール

インストールは至って簡単だ。

npm i -g angular-cli

node_modulesのグローバル空間にインストールするため抵抗がある人もいるかもしれないが、その場合適宜工夫してもらいたい。基本的にコマンドラインツールとして使うので、筆者はあまり問題ないと考えているが、バージョン違いによる挙動の差異があり得るため、チームで運用する際はメンバー間でバージョンについて注意されたい。

angular-cliの必然性

拙記事に『npm iしてAngualr 2のHello World!を書くところまで』というものがあるが、現時点ではこの記事の内容よりangular-cliによって生成された基礎の方が使いやすい。利点を挙げる。

  • Angular公式が良と考えているディレクトリ構成に従える
  • webpackなどのバンドラーの環境構築を自動で行うため意識しなくてよい
    • ng serveによる開発中のブラウザ上でのプレビューが高速
    • AoTを前提としているビルド
  • Lintやテストの環境構築が不要ですぐに利用可能

一方で、これらはそのまま欠点にもなりうる。

  • 独自のドメインレイヤーが多い場合どこに置けばいいかの指標をangular-cliは提示しない
  • Angularで完結しない場合、たとえばBabelのトランスパイルを別途挟む必要があるときなど、webpackの設定がブラックボックス化されているため拡張が面倒
  • LintはTSLint、テストフレームワークはJasmineに固定される

ただし、JavaScriptやAngularの熟練者であれどもangular-cliが提供するメリットがこれらの欠点を上回っている(と筆者は感じている)ため、多少の束縛を踏まえてangular-cliを前提とした開発フローを検討するほうがよい。

新規作成

angular-cliを用いたアプリケーションの新規作成はコマンドで行える。

ng new myapp

myappは任意のアプリケーション名だ。このコマンドを実行することでnpmによるインストールと各種ファイルの自動生成を行う。では、このときの生成結果をみてみよう。なお、本稿でのangular-cliのバージョンは1.0.0-beta.24である。

.
├── README.md
├── angular-cli.json
├── e2e
│   ├── app.e2e-spec.ts
│   ├── app.po.ts
│   └── tsconfig.json
├── karma.conf.js
├── node_modules
│   ├── @angular
│   │   ├── common
│   │   ├── compiler
│   │   ├── compiler-cli
│   │   ├── core
│   │   ├── forms
│   │   ├── http
│   │   ├── platform-browser
│   │   ├── platform-browser-dynamic
│   │   ├── router
│   │   └── tsc-wrapped
│   ├── @angular-cli
│   │   ├── ast-tools
│   │   └── base-href-webpack
│   ├── @ngtools
│   │   └── webpack
│   ├── @types
│   │   ├── jasmine
│   │   ├── node
│   │   ├── q
│   │   └── selenium-webdriver
│   ├── abbrev

省略

├── package.json
├── protractor.conf.js
├── src
│   ├── app
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   ├── test.ts
│   └── tsconfig.json
└── tslint.json
  • angular-cli.json
  • e2e/
  • karma.conf.js
  • protractor.conf.js
  • tslint.json

これらの自動生成が特に嬉しい。e2e周りは、環境構築を怠りそのまま実施しない例も多いと予想するので、最初から準備されているならば取り組みやすいだろう。.gitignoreも生成されているのは地味に嬉しい点だ。

次にsrc/内をみていく。

.
├── app
│   ├── app.component.css
│   ├── app.component.html
│   ├── app.component.spec.ts
│   ├── app.component.ts
│   └── app.module.ts
├── assets
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── tsconfig.json

基本的にはapp/以下にアプリケーションのコードが格納されるという認識でよい。

  • assets/
    • 画像や、どのComponentにも紐つかないCSS、i18n言語ファイルなどを格納すればいいはず
  • index.html, main.ts, styles.css, test.ts
    • それぞれのエントリポイントとなるファイル
    • 特にtest.tsは自前で記述するとかなり面倒なので助かる

各種自動生成のチートシート

本稿の主題でもあるチートシートを記録する。angular-cliではng g component my-new-componentのようにコマンドを実行することで、そのAngularアプリケーションに新しくComponentやServiceを追加できる。そのときの生成先や生成ファイル数などを思い出すためのものだ。

ng g component

ng g component [name]はComponentを新規に作成する。

ng g component foo-bar-baz
installing component
  create src/app/foo-bar-baz/foo-bar-baz.component.css
  create src/app/foo-bar-baz/foo-bar-baz.component.html
  create src/app/foo-bar-baz/foo-bar-baz.component.spec.ts
  create src/app/foo-bar-baz/foo-bar-baz.component.ts
  update src/app/app.module.ts

標準では、常にcss, html, テスト用spec.ts, tsの4つをセットで生成し、それらを1ディレクトリに格納する。ng g component似続けてキャメルケース(CamelCase)を入力しても自動的にケバブケース(kebab-case)に変換される。ここにmy-componentなどと入力すればMyComponentComponentというclassが出力されるため、componentを名前に含む必要はない。

foo-bar-baz.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-foo-bar-baz',
  templateUrl: './foo-bar-baz.component.html',
  styleUrls: ['./foo-bar-baz.component.css']
})
export class FooBarBazComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

tsではselectorに常にapp-接頭辞が付与される。templateUrlstyleUrlsは自動的に記述されている。そしてapp.module.tsに自動的にこのComponentが読み込まれるようになっている。

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { FooBarBazComponent } from './foo-bar-baz/foo-bar-baz.component';

@NgModule({
  declarations: [
    AppComponent,
    FooBarBazComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

ng g directive

ng g directive [name]はDirectiveを新規に作成する。

ng g directive foo
installing directive
  create src/app/foo.directive.spec.ts
  create src/app/foo.directive.ts
  update src/app/app.module.ts

標準的にはapp/直下に配置されることに注意されたい。これを回避したい場合、例えばapp/directives/下に配置したければ次のようにする。

mkdir ./src/app/directives && cd ./src/app/directives
ng g directive foo
installing directive
  create src/app/directives/foo.directive.spec.ts
  create src/app/directives/foo.directive.ts
  update src/app/app.module.ts

要するにCurrent Directoryを考慮して生成する。

pwd
/Users/armorik83/Desktop/myapp
ng g directive foo --flat false
installing directive
  create src/app/foo/foo.directive.spec.ts
  create src/app/foo/foo.directive.ts
  update src/app/app.module.ts

Rootで--flat falseを付けることで[name]/[name].directive.tsを生成する。このオプションもCurrent Directoryを考慮する。オプションについては次節にてまとめる。

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

@Directive({
  selector: '[appFoo]'
})
export class FooDirective {

  constructor() { }

}

ts側はselector[appFoo]とキャメルケース指定になっている点に注意だ。

ng g pipe

ng g pipe [name]はPipeを新規に作成する。Directiveとほぼ同様である。

ng g pipe foo
installing pipe
  create src/app/foo.pipe.spec.ts
  create src/app/foo.pipe.ts
  update src/app/app.module.ts
foo.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'foo'
})
export class FooPipe implements PipeTransform {

  transform(value: any, args?: any): any {
    return null;
  }

}

ng g service

ng g service [name]Injectableなclassを新規に作成する。

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

ディレクトリ構成だが、app/以下に直接配置するのは規模拡大時に大量に並ぶことが想定されるため避けたい。筆者の経験則としてはapp/services/とするかapp/foo/foo.component.tsと同階層にapp/foo/foo.service.tsとしたい。この辺りは作成しようとするServiceの性質に合わせて考えればよいだろう。

そしてWARNING Service is generated but not provided, it must be provided to be usedとあるように、app.module.tsに自動で追記されない点には注意すべきである。これはServiceを一律でprovideするかComponent単位でprovideするか開発者が判断せねばならないためで、自動化が困難であることに由来する。オプションで自動追記をしてくれないかと探したが、現時点ではそのようなオプションは用意されていなかった。

foo.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class FooService {

  constructor() { }

}

ts出力側はシンプルだ。

ng g class

ng g class [name]はclassを新規に作成する。

ng g class foo
installing class
  create src/app/foo.ts

このコマンドの真価は--specオプションにある。

ng g class bar --spec
installing class
  create src/app/bar.spec.ts
  create src/app/bar.ts

ng g classにおいてはspecファイルの自動生成がオフのため、オプションを付与する必要がある(angular-cli.jsonの設定を変更することでも設定が可能)。

bar.ts
export class Bar {
}
bar.spec.ts
import {Bar} from './bar';

describe('Bar', () => {
  it('should create an instance', () => {
    expect(new Bar()).toBeTruthy();
  });
});

わずかこれだけだが、この手間すら面倒がる開発者はきっといるはずだ。

ng g interface

ng g interface [name]はTypeScriptのinterfaceを新規に作成する。

ng g interface foo
installing interface
  create src/app/foo.ts
foo.ts
export interface Foo {
}

複数のコマンドを繋いで一気に生成したい場合などは使えるかもしれないが、単体で使うには結果が寂しい。なお、オプションは定義されていない。

ng g enum

ng g enum [name]はTypeScriptのenumを新規に作成する。

ng g enum foo
installing enum
  create src/app/foo.enum.ts
foo.enum.ts
export enum Foo {
}

TypeScriptのenum自体がいまいち扱いにくいため、個人的にはあまり使うことが無さそうだ。

ng g module

ng g module [name]NgModuleを新規に作成する。ECMAScriptやNode.jsの文脈でのmoduleではない点には気をつけておく。

ng g module foo
installing module
  create src/app/foo/foo.module.ts
foo.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class FooModule { }

あまり使うことはないが、覚えておいて損はない。

以上がng gによる生成をまとめたものだ。

生成オプションのチートシート

ng gではオプションが利用できる。実例は先にng g directiveの項にて述べた。それぞれのオプションはangular-cli/packages/angular-cli/blueprintsにて定義されている。該当箇所を下記にまとめる。

--specのようにオプションのnameをハイフン2つで追記すると有効になる。falseの場合は--flat falseのようにする。型がStringならばng g component foo --prefix myのように直接指定すればよい(この場合selector: 'my-foo'となる)。オプション名は分かりやすく名付けられているので、一つ一つの説明は割愛する。

特筆する事項

スタイルシートの言語選択

CSSではなくSassなどの言語を利用したい場合はオプションで切り替えが可能である。

ng new sassy-project --style=sass

プロジェクトの途中で切り替えたい場合は設定ファイルを変更する。

ng set defaults.styleExt scss

独自のテンプレートで出力したいとき

あまり推奨しないが、テンプレートを改変することも可能だ。たとえばComponentのtsファイルの書式を改変したければ、以下のパスのファイルを変更すればよい。

./node_modules/angular-cli/blueprints/component/files/__path__/__name__.component.ts
import { Component, OnInit<% if(viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection) { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';

@Component({
  selector: '<%= selector %>',<% if(inlineTemplate) { %>
  template: `
    <p>
      <%= dasherizedModuleName %> Works!
    </p>
  `,<% } else { %>
  templateUrl: './<%= dasherizedModuleName %>.component.html',<% } if(inlineStyle) { %>
  styles: []<% } else { %>
  styleUrls: ['./<%= dasherizedModuleName %>.component.<%= styleExt %>']<% } %><% if(viewEncapsulation) { %>,
  encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection) { %>,
  changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= classifiedModuleName %>Component implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

この1行目を次のように変更してみる。

import {Component, OnInit<% if(viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection) { %>, ChangeDetectionStrategy<% }%>} from '@angular/core'

すると出力はこのようになる。

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

「俺にはセミコロンは不要だ」などという過激派は試してみてもいいだろう。くれぐれもチームで扱う場合は合意を取るように。

まとめ

このように、オプションの使い方を覚えると自動生成にも柔軟性が出てくるので、angular-cliの生成挙動が気に入らない場合も自分好みに対応できることが分かった。AoTコンパイルやテスト環境の構築など手間を省ける利点は多いので、angular-cliは引き続き推奨していきたい。

それではよいお年を。