Angular

Angularによるスライドアニメーション

Angularでいわゆるスライドアニメーションを実装するための方法です。

Angular 4 +での初期設定

Angular4 よりanimation系は@angular/coreより除外されていますので、@NgModule内で@angular/platform-browser/animationsより取得したBrowserAnimationsModuleをimportsに設定しておき、@angular/animations より trigger, state,style, transition, animate, keyframesをimportしておきます。

import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; . . . @NgModule({ declarations: [ App, ], imports: [ ... BrowserAnimationsModule, ... ], bootstrap: [App] }) import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';

Angular 2での初期設定

Angular2系ではanimation系がまだ@angular/coreに含まれているので@angular/coreよりstate, trigger, style, transition, animateをインポートするだけでよいです。

import { state, trigger, style, transition, animate} from "@angular/core";

共通設定

Angularでは@Componentにanimationsとしてアニメーションを設定することができます。

下記のサンプルではtrigger()に指定できるアニメーション名に「slide」と指定をして、状態が「show」から「hide」になる場合は高さが0に、状態が「hide」から「show」に変更される場合は高さがautoになるようにアニメーションを設定しています。

アニメーション対象の要素(今回はul要素)に 「[@アニメーション名] = アニメーション状態」としてアニメーションが設定でき下記のサンプルではmySlideStateをアニメーション状態として定義しています。

最後にアニメーション対象の要素にCSSで「overflow:hidden」を指定すれば完了です。

動作サンプル

@Component({ selector: 'app', template: ` <input (click)="mySlideState = 'show'" type="button" value="開く" /> <input (click)="mySlideState = 'hide'" type="button" value="閉じる" /> <ul [@slide]="mySlideState"> <li>list1</li> <li>list2</li> <li>list3</li> <li>list4</li> <li>list5</li> </ul> `, styles: [` ul{ overflow:hidden } `], animations:[ trigger('slide',[ state('show', style({ height:'*' })), state('hide', style({ height:'0' })), transition('show => hide', animate('400ms ease-in')), transition('hide => show', animate('400ms ease-out')) ]) ] }) export class App { mySlideState:string = 'hide'; }

これでアニメーション状態が「show」から「hide」、「hide」から「show」に変わるたびにスライドアニメーションが行われます。

Angularによるスライドアニメーション

Angularでいわゆるスライドアニメーションを実装するための方法です。

Angular 4 +での初期設定

Angular4 よりanimation系は@angular/coreより除外されていますので、@NgModule内で@angular/platform-browser/animationsより取得したBrowserAnimationsModuleをimportsに設定しておき、@angular/animations より trigger, state,style, transition, animate, keyframesをimportしておきます。

import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; . . . @NgModule({ declarations: [ App, ], imports: [ ... BrowserAnimationsModule, ... ], bootstrap: [App] }) import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';

Angular 2での初期設定

Angular2系ではanimation系がまだ@angular/coreに含まれているので@angular/coreよりstate, trigger, style, transition, animateをインポートするだけでよいです。

import { state, trigger, style, transition, animate} from "@angular/core";

共通設定

Angularでは@Componentにanimationsとしてアニメーションを設定することができます。

下記のサンプルではtrigger()に指定できるアニメーション名に「slide」と指定をして、状態が「show」から「hide」になる場合は高さが0に、状態が「hide」から「show」に変更される場合は高さがautoになるようにアニメーションを設定しています。

アニメーション対象の要素(今回はul要素)に 「[@アニメーション名] = アニメーション状態」としてアニメーションが設定でき下記のサンプルではmySlideStateをアニメーション状態として定義しています。

最後にアニメーション対象の要素にCSSで「overflow:hidden」を指定すれば完了です。

動作サンプル

@Component({ selector: 'app', template: ` <input (click)="mySlideState = 'show'" type="button" value="開く" /> <input (click)="mySlideState = 'hide'" type="button" value="閉じる" /> <ul [@slide]="mySlideState"> <li>list1</li> <li>list2</li> <li>list3</li> <li>list4</li> <li>list5</li> </ul> `, styles: [` ul{ overflow:hidden } `], animations:[ trigger('slide',[ state('show', style({ height:'*' })), state('hide', style({ height:'0' })), transition('show => hide', animate('400ms ease-in')), transition('hide => show', animate('400ms ease-out')) ]) ] }) export class App { mySlideState:string = 'hide'; }

これでアニメーション状態が「show」から「hide」、「hide」から「show」に変わるたびにスライドアニメーションが行われます。

続:AngularでChangeDetectionによるパフォーマンスチューニング

前回(AngularでChangeDetectionによるパフォーマンスチューニング)は、ChangeDetectionStrategy.OnPushによるチューニングについて解説しましたが今回はその続きです。

@Input()などで親コンポーネントから値を受け取るケースでは注意が必要です。

プリミティブ値の場合

@Input()で渡す値がプリミティブ値の場合は問題なく特に気にする必要はありません。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message}}</p> `, }) export class Child1 { @Input() message: string; } @Component({ selector: 'child2', template: ` <p>{{message}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: string; } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message"></p> <child1 [message]="message"></child1> <child2 [message]="message"></child2> </div>` }) export class App { message:string; }

サンプルでは親コンポーネントで入力された値を子コンポーネントに引き渡しています。ChangeDetectionStrategy.OnPushの有無に関わらず子コンポーネントは正常に描画されています。

オブジェクトの場合

@Input()で渡す値がオブジェクトになるとオブジェクトの内容が変化しても、ChangeDetectionStrategy.OnPushを指定したコンポーネントには変更内容が反映されません。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: {name: string}; } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message.name"></p> <child1 [message]="message"></child1> <child2 [message]="message"></child2> </div>` }) export class App { message: {name: string} = {name:null}; }

このサンプルではmessageオブジェクトのnameプロパティが更新されていますが、ChangeDetectionStrategy.OnPushを指定したコンポーネントは変更の感知ができていません。

これは、オブジェクトの参照自体が変更されていないためAngularが@Input()で受け取っている値が変更されている事を検知できないためです。

Observableで値を更新

これを回避するためには@Input()で渡す値をオブジェクトではなくObservableを引き渡すように変更してsubscribeで変更を検知して、ChangeDetectorRef.markForCheck();を実行することでChangeDetectionStrategy.OnPushを指定したコンポーネントには変更内容を反映させることができます。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() data: Observable<any>; message: {name: string} = {name:null}; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.data.subscribe(value => { console.log(value.message) this.message = value.message; this.cd.markForCheck(); }); } } @Component({ selector: 'app', template: `<div> <p><input type="text" (input)="update()" [(ngModel)]="message.name"></p> <child1 [message]="message"></child1> <child2 [data]="data$"></child2> </div>` }) export class App { message: {name: string} = {name:null}; data$ = new BehaviorSubject({ message:this.message }); update(){ this.data$.next({ message:this.message }); } }

子コンポーネントの再描画用のObservable

巨大なオブジェクトを元にコンポーネントを構築している場合など、オブジェクトをObservableとして引き渡すのは少し面倒くさいケースというのもあります。 また、オブジェクトの変更のタイミングが必ずしも子コンポーネントの更新のタイミングとは限りません。

そういったケースでは子コンポーネントの更新用のObservableを用意して、手動で更新することで任意のタイミングで子コンポーネントの再描画を制御することも可能です。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: {name: string}; @Input() update: Observable<any>; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.update.subscribe(value => { this.cd.markForCheck(); }); } } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message.name"></p> <p><input type="button" value="update" (click)="update()" /></p> <child1 [message]="message"></child1> <child2 [message]="message" [update]="update$"></child2> </div>` }) export class App { message: {name: string} = {name:null}; _counter:number=0; update$ = new BehaviorSubject({ counter:this._counter }); update(){ this.update$.next({ counter:++this._counter}); } }

サンプルではmessageの更新のタイミングではなくupdateボタンが押下されたタイミングで子コンポーネントの描画を行なっています。

関連エントリー

SystemJSでAngular 2の環境を構築する

続:AngularでChangeDetectionによるパフォーマンスチューニング

前回(AngularでChangeDetectionによるパフォーマンスチューニング)は、ChangeDetectionStrategy.OnPushによるチューニングについて解説しましたが今回はその続きです。

@Input()などで親コンポーネントから値を受け取るケースでは注意が必要です。

プリミティブ値の場合

@Input()で渡す値がプリミティブ値の場合は問題なく特に気にする必要はありません。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message}}</p> `, }) export class Child1 { @Input() message: string; } @Component({ selector: 'child2', template: ` <p>{{message}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: string; } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message"></p> <child1 [message]="message"></child1> <child2 [message]="message"></child2> </div>` }) export class App { message:string; }

サンプルでは親コンポーネントで入力された値を子コンポーネントに引き渡しています。ChangeDetectionStrategy.OnPushの有無に関わらず子コンポーネントは正常に描画されています。

オブジェクトの場合

@Input()で渡す値がオブジェクトになるとオブジェクトの内容が変化しても、ChangeDetectionStrategy.OnPushを指定したコンポーネントには変更内容が反映されません。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: {name: string}; } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message.name"></p> <child1 [message]="message"></child1> <child2 [message]="message"></child2> </div>` }) export class App { message: {name: string} = {name:null}; }

このサンプルではmessageオブジェクトのnameプロパティが更新されていますが、ChangeDetectionStrategy.OnPushを指定したコンポーネントは変更の感知ができていません。

これは、オブジェクトの参照自体が変更されていないためAngularが@Input()で受け取っている値が変更されている事を検知できないためです。

Observableで値を更新

これを回避するためには@Input()で渡す値をオブジェクトではなくObservableを引き渡すように変更してsubscribeで変更を検知して、ChangeDetectorRef.markForCheck();を実行することでChangeDetectionStrategy.OnPushを指定したコンポーネントには変更内容を反映させることができます。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() data: Observable<any>; message: {name: string} = {name:null}; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.data.subscribe(value => { console.log(value.message) this.message = value.message; this.cd.markForCheck(); }); } } @Component({ selector: 'app', template: `<div> <p><input type="text" (input)="update()" [(ngModel)]="message.name"></p> <child1 [message]="message"></child1> <child2 [data]="data$"></child2> </div>` }) export class App { message: {name: string} = {name:null}; data$ = new BehaviorSubject({ message:this.message }); update(){ this.data$.next({ message:this.message }); } }

子コンポーネントの再描画用のObservable

巨大なオブジェクトを元にコンポーネントを構築している場合など、オブジェクトをObservableとして引き渡すのは少し面倒くさいケースというのもあります。 また、オブジェクトの変更のタイミングが必ずしも子コンポーネントの更新のタイミングとは限りません。

そういったケースでは子コンポーネントの更新用のObservableを用意して、手動で更新することで任意のタイミングで子コンポーネントの再描画を制御することも可能です。

サンプル

@Component({ selector: 'child1', template: ` <p>{{message.name}}</p> `, }) export class Child1 { @Input() message: {name: string}; } @Component({ selector: 'child2', template: ` <p>{{message.name}}</p> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { @Input() message: {name: string}; @Input() update: Observable<any>; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.update.subscribe(value => { this.cd.markForCheck(); }); } } @Component({ selector: 'app', template: `<div> <p><input type="text" [(ngModel)]="message.name"></p> <p><input type="button" value="update" (click)="update()" /></p> <child1 [message]="message"></child1> <child2 [message]="message" [update]="update$"></child2> </div>` }) export class App { message: {name: string} = {name:null}; _counter:number=0; update$ = new BehaviorSubject({ counter:this._counter }); update(){ this.update$.next({ counter:++this._counter}); } }

サンプルではmessageの更新のタイミングではなくupdateボタンが押下されたタイミングで子コンポーネントの描画を行なっています。

関連エントリー

SystemJSでAngular 2の環境を構築する

AngularでChangeDetectionによるパフォーマンスチューニング

AngularでChangeDetectionによるパフォーマンスチューニングを行う方法です。

ChangeDetectionとは?

AngularではChangeDetectionと呼ばれる更新機構を持ってコンポーネントの情報が変更されるとページの再描画が行われます。

くわしい仕組みに関しては以下を参考にしてください。

Angular2のChange Detectionについて - Qiita

パフォーマンスチューニング

Angularのデフォルト設定では、全てのコンポーネントの変更に対して、このChangeDetectionによる更新が毎回走るようになっている。

例えば以下のようなコンポーネント構成時、つまりAコンポーネントの子コンポーネントとしてBコンポーネントとCコンポーネントが配置されているようなケースで、Bコンポーネントに対して変更が行われると本来更新不要なCコンポーネントに対する再描画処理も走ってしまいます。

ただ、AngularのVirtual DOM機構が上手に更新を行なってくれているらしく、本来ならここらへんは実装者は意識せずに構築ができる(はず)

ただ、大掛かりなHTMLレンダリングやメソッド等を利用した複雑な描画条件が指定されたコンポーネントが存在し、連続してChangeDetectionが呼ばれるようなケースではアプリケーションが重くなってしまうため対応しなくてはいけません。

つまり、上記の図ではCコンポーネントが描画コストが高い場合に、AコンポーネントやBコンポーネントなどCコンポーネントとは関係ない値が更新された際にはCコンポーネントの描画を行わないようにすることでアプリケーションが重くなってしまうのを防ぐことができます。

対応策は簡単で@Component()でコンポーネントを作成時に「changeDetection:ChangeDetectionStrategy.OnPush」の指定を行います。

@Component({ selector: ..., template: ...., changeDetection:ChangeDetectionStrategy.OnPush })

以下が動作サンプルです。

サンプル

このサンプルではappコンポーネント配下にchild1コンポーネント、child2コンポーネントを配置して、child2コンポーネントには「changeDetection:ChangeDetectionStrategy.OnPush」の指定を行い、それぞれのコンポーネントには自身を更新するためのカウントアップボタンを配置して更新時間を表示しています。

child2コンポーネントの更新時間は自身のボタンが押されないと更新されないのに対して、appコンポーネント、child1コンポーネントはどのボタンが押下されても更新されているのがわかります。

以下がソースコードです。

@Component({ selector: 'child1', template: ` <h1>child1:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> `, }) export class Child1 { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } } @Component({ selector: 'child2', template: ` <h1>child2:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } } @Component({ selector: 'app', template: `<div> <h1>app:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> <child1></child1> <child2></child2> </div>`, changeDetection:ChangeDetectionStrategy.OnPush, }) export class App { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } }

このように任意のタイミングでしか更新したくないコンポーネントに対してchangeDetection:ChangeDetectionStrategy.OnPushを指定することでパフォーマンスのチューニングを行うことができます。

構築するアプリケーションによりますが、複雑なアプリケーションなら最初からコンポーネントに対してchangeDetection:ChangeDetectionStrategy.OnPushを指定しておくのもよいでしょう。

続:AngularでChangeDetectionによるパフォーマンスチューニングに続きます。

関連エントリー

SystemJSでAngular 2の環境を構築する

AngularでChangeDetectionによるパフォーマンスチューニング

AngularでChangeDetectionによるパフォーマンスチューニングを行う方法です。

ChangeDetectionとは?

AngularではChangeDetectionと呼ばれる更新機構を持ってコンポーネントの情報が変更されるとページの再描画が行われます。

くわしい仕組みに関しては以下を参考にしてください。

Angular2のChange Detectionについて - Qiita

パフォーマンスチューニング

Angularのデフォルト設定では、全てのコンポーネントの変更に対して、このChangeDetectionによる更新が毎回走るようになっている。

例えば以下のようなコンポーネント構成時、つまりAコンポーネントの子コンポーネントとしてBコンポーネントとCコンポーネントが配置されているようなケースで、Bコンポーネントに対して変更が行われると本来更新不要なCコンポーネントに対する再描画処理も走ってしまいます。

ただ、AngularのVirtual DOM機構が上手に更新を行なってくれているらしく、本来ならここらへんは実装者は意識せずに構築ができる(はず)

ただ、大掛かりなHTMLレンダリングやメソッド等を利用した複雑な描画条件が指定されたコンポーネントが存在し、連続してChangeDetectionが呼ばれるようなケースではアプリケーションが重くなってしまうため対応しなくてはいけません。

つまり、上記の図ではCコンポーネントが描画コストが高い場合に、AコンポーネントやBコンポーネントなどCコンポーネントとは関係ない値が更新された際にはCコンポーネントの描画を行わないようにすることでアプリケーションが重くなってしまうのを防ぐことができます。

対応策は簡単で@Component()でコンポーネントを作成時に「changeDetection:ChangeDetectionStrategy.OnPush」の指定を行います。

@Component({ selector: ..., template: ...., changeDetection:ChangeDetectionStrategy.OnPush })

以下が動作サンプルです。

サンプル

このサンプルではappコンポーネント配下にchild1コンポーネント、child2コンポーネントを配置して、child2コンポーネントには「changeDetection:ChangeDetectionStrategy.OnPush」の指定を行い、それぞれのコンポーネントには自身を更新するためのカウントアップボタンを配置して更新時間を表示しています。

child2コンポーネントの更新時間は自身のボタンが押されないと更新されないのに対して、appコンポーネント、child1コンポーネントはどのボタンが押下されても更新されているのがわかります。

以下がソースコードです。

@Component({ selector: 'child1', template: ` <h1>child1:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> `, }) export class Child1 { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } } @Component({ selector: 'child2', template: ` <h1>child2:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> `, changeDetection:ChangeDetectionStrategy.OnPush }) export class Child2 { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } } @Component({ selector: 'app', template: `<div> <h1>app:{{count}}</h1> <p>{{lastUpdated()}}</p> <input type='button' value='countup' (click)='countup()'/> <child1></child1> <child2></child2> </div>`, changeDetection:ChangeDetectionStrategy.OnPush, }) export class App { count:number = 0; countup():void{ this.count++; } lastUpdated():String{ return Date().toString(); } }

このように任意のタイミングでしか更新したくないコンポーネントに対してchangeDetection:ChangeDetectionStrategy.OnPushを指定することでパフォーマンスのチューニングを行うことができます。

構築するアプリケーションによりますが、複雑なアプリケーションなら最初からコンポーネントに対してchangeDetection:ChangeDetectionStrategy.OnPushを指定しておくのもよいでしょう。

続:AngularでChangeDetectionによるパフォーマンスチューニングに続きます。

関連エントリー

SystemJSでAngular 2の環境を構築する

SystemJSでAngular 2の環境を構築する

SystemJSwebpackBrowserifyのようなJavaScriptファイルの依存関係を解決するためのモジュール管理ツールです。

Angular 2ではquickstartではSystemJSを利用したコンパイル環境を、Angular CLIではwebpackを利用したコンパイル環境を提供しています。

今回は用意されている環境を利用せずに0ベースでSystemJSの環境を構築していく方法を解説します。 (quickstartの設定内容を最小限にして順序立てて解説しています)

package.jsonの作成

まずはターミナルなどで以下のコマンドを入力してpackage.jsonの作成を行います。

npm init -y

ローカルサーバーの構築

SystemJSではAjaxでファイルの取得などを行うためローカルサーバーの設定が必要になります。

以下のコマンドでlite-serverのインストールを行い、

npm install lite-server --save-dev

設定ファイルのbs-config.jsonを作成します。

{ "server": { "baseDir": "src", "routes": { "/node_modules": "node_modules" } } }

今回はsrcフォルダを内容をローカルに表示し、node_modulesにもアクセスできるようにします。

ひとまず、確認用にsrc/index.htmlを作成します。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>Hello World!</h1> </body> </html>

package.jsonのscriptsにはローカルサーバー起動用のserveコマンドを登録しておきます。

"scripts": { "serve": "lite-server -c=bs-config.json" },

これで以下のコマンドでローカルサーバーが立ち上がります。

npm run serve

TypeScriptのコンパイル環境の構築

次にTypeScriptのコンパイル環境を構築しましょう。

以下のコマンドでTypescriptのインストールを行い

npm install typescript --save-dev

srcディレクトリの中にTypeScriptの設定ファイルtsconfig.jsonを作成します。

{ "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2015", "dom" ], "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true } }

package.jsonのscriptsにはビルド用のコマンドと監視ビルド用のコマンドを登録しておきます。

"scripts": { "serve": "lite-server -c=bs-config.json", "build": "tsc -p src/", "build:watch": "tsc -p src/ -w" },

これで以下のコマンドでTypeScriptのコンパイルが可能になります。

npm run build

試しに./src/main.tsファイルを作成してコマンドを入力してみましょう。

let foo:String = 'ok' console.log(foo);

コンパイルされた./src/main.jsファイルと./src/main.js.mapファイルが出力されるはずです。

var foo = 'ok'; console.log(foo); //# sourceMappingURL=app.js.map

.tsファイルを監視しておき変更があれば即座にコンパイルを行うということをしたいのであれば以下のコマンドを実行します。

npm run build:watch

サーバー起動とコンパイル処理を並列で実行

サーバー起動の「npm run serve」とコンパイル処理の「npm run build:watch」を同時に行うようにしましょう。

以下のコマンドでconcurrentlyのインストールを行い、

npm install concurrently --save-dev

package.jsonにstartコマンドを追加します。

"scripts": { "serve": "lite-server -c=bs-config.json", "build": "tsc -p src/", "build:watch": "tsc -p src/ -w", "start": "concurrently \"npm run build:watch\" \"npm run serve\"" },

これで以下のコマンドでローカルサーバーとコンパイル環境が立ち上がります。

npm start

SystemJSによるファイル読み込み

次にSystemJSによるファイルの読み込みを行います。

まずはsystemjsのインストール。

npm install systemjs --save

次に設定ファイルをsrc/systemjs.config.jsとして作成します。

(function (global) { System.config({ paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { // our app is within the app folder app: 'app', // angular bundles '@angular/core': 'npm:@angular/core/bundles/core.umd.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '@angular/http': 'npm:@angular/http/bundles/http.umd.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', // other libraries 'rxjs': 'npm:rxjs', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { app: { defaultExtension: 'js' }, rxjs: { defaultExtension: 'js' } } }); })(this);

最後にsrc/index.htmlでこれらのファイルを読み込み実行しましょう。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>Hello World!</h1> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </body> </html>

これでローカルサイト内でsrc/main.ts内に記述していた内容「cosnole.log('ok')」が出力されます。

Angular 2との連携

それでは最後にAngular 2でブラウザ上にHellow Angularと出力するまで解説します。 (Angularのコードの内容は解説しません)

まずは、必要ファイル(@angularとrxjs、core-js)のインストールを行います。

npm install @angular/{core,common,compiler,platform-browser,platform-browser-dynamic} rxjs zone.js core-js --save

つぎにsrc/index.htmlの内容を修正します。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <my-app>Loading…</my-app> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </body> </html>

rxjsとcore-jsを読み込み、my-appコンポーネントを配置しています。

次にsrc/main.tsの内容を修正

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

次にsrc/app/app.module.tsを作成します。

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser';    import { AppComponent } from './app.component';    @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

最後にsrc/app/app.component.tsを作成します。

import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>`, }) export class AppComponent { name = 'Angular'; }

これでブラウザ上にHellow Angularと出力されます。

最近ではquickstartやAngular-CLIのような便利なツールがありますが、行われている設定や設定ファイルなどを理解しておくと、なにかあった時に役にたちますので。

作成したものはGitHubにあげてますのでご利用ください。

関連エントリー

anyenv+ndenvでプロジェクトごとにnode.jsのバージョンを切り替える webpackでJavaScriptライブラリを利用する npmとBrowserifyでjQueryプラグインを管理する babelifyで始めるES6 Babelで始めるES6入門

SystemJSでAngular 2の環境を構築する

SystemJSwebpackBrowserifyのようなJavaScriptファイルの依存関係を解決するためのモジュール管理ツールです。

Angular 2ではquickstartではSystemJSを利用したコンパイル環境を、Angular CLIではwebpackを利用したコンパイル環境を提供しています。

今回は用意されている環境を利用せずに0ベースでSystemJSの環境を構築していく方法を解説します。 (quickstartの設定内容を最小限にして順序立てて解説しています)

package.jsonの作成

まずはターミナルなどで以下のコマンドを入力してpackage.jsonの作成を行います。

npm init -y

ローカルサーバーの構築

SystemJSではAjaxでファイルの取得などを行うためローカルサーバーの設定が必要になります。

以下のコマンドでlite-serverのインストールを行い、

npm install lite-server --save-dev

設定ファイルのbs-config.jsonを作成します。

{ "server": { "baseDir": "src", "routes": { "/node_modules": "node_modules" } } }

今回はsrcフォルダを内容をローカルに表示し、node_modulesにもアクセスできるようにします。

ひとまず、確認用にsrc/index.htmlを作成します。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>Hello World!</h1> </body> </html>

package.jsonのscriptsにはローカルサーバー起動用のserveコマンドを登録しておきます。

"scripts": { "serve": "lite-server -c=bs-config.json" },

これで以下のコマンドでローカルサーバーが立ち上がります。

npm run serve

TypeScriptのコンパイル環境の構築

次にTypeScriptのコンパイル環境を構築しましょう。

以下のコマンドでTypescriptのインストールを行い

npm install typescript --save-dev

srcディレクトリの中にTypeScriptの設定ファイルtsconfig.jsonを作成します。

{ "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2015", "dom" ], "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true } }

package.jsonのscriptsにはビルド用のコマンドと監視ビルド用のコマンドを登録しておきます。

"scripts": { "serve": "lite-server -c=bs-config.json", "build": "tsc -p src/", "build:watch": "tsc -p src/ -w" },

これで以下のコマンドでTypeScriptのコンパイルが可能になります。

npm run build

試しに./src/main.tsファイルを作成してコマンドを入力してみましょう。

let foo:String = 'ok' console.log(foo);

コンパイルされた./src/main.jsファイルと./src/main.js.mapファイルが出力されるはずです。

var foo = 'ok'; console.log(foo); //# sourceMappingURL=app.js.map

.tsファイルを監視しておき変更があれば即座にコンパイルを行うということをしたいのであれば以下のコマンドを実行します。

npm run build:watch

サーバー起動とコンパイル処理を並列で実行

サーバー起動の「npm run serve」とコンパイル処理の「npm run build:watch」を同時に行うようにしましょう。

以下のコマンドでconcurrentlyのインストールを行い、

npm install concurrently --save-dev

package.jsonにstartコマンドを追加します。

"scripts": { "serve": "lite-server -c=bs-config.json", "build": "tsc -p src/", "build:watch": "tsc -p src/ -w", "start": "concurrently \"npm run build:watch\" \"npm run serve\"" },

これで以下のコマンドでローカルサーバーとコンパイル環境が立ち上がります。

npm start

SystemJSによるファイル読み込み

次にSystemJSによるファイルの読み込みを行います。

まずはsystemjsのインストール。

npm install systemjs --save

次に設定ファイルをsrc/systemjs.config.jsとして作成します。

(function (global) { System.config({ paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { // our app is within the app folder app: 'app', // angular bundles '@angular/core': 'npm:@angular/core/bundles/core.umd.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '@angular/http': 'npm:@angular/http/bundles/http.umd.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', // other libraries 'rxjs': 'npm:rxjs', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { app: { defaultExtension: 'js' }, rxjs: { defaultExtension: 'js' } } }); })(this);

最後にsrc/index.htmlでこれらのファイルを読み込み実行しましょう。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>Hello World!</h1> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </body> </html>

これでローカルサイト内でsrc/main.ts内に記述していた内容「cosnole.log('ok')」が出力されます。

Angular 2との連携

それでは最後にAngular 2でブラウザ上にHellow Angularと出力するまで解説します。 (Angularのコードの内容は解説しません)

まずは、必要ファイル(@angularとrxjs、core-js)のインストールを行います。

npm install @angular/{core,common,compiler,platform-browser,platform-browser-dynamic} rxjs zone.js core-js --save

つぎにsrc/index.htmlの内容を修正します。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <my-app>Loading…</my-app> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </body> </html>

rxjsとcore-jsを読み込み、my-appコンポーネントを配置しています。

次にsrc/main.tsの内容を修正

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

次にsrc/app/app.module.tsを作成します。

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser';    import { AppComponent } from './app.component';    @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }

最後にsrc/app/app.component.tsを作成します。

import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>`, }) export class AppComponent { name = 'Angular'; }

これでブラウザ上にHellow Angularと出力されます。

最近ではquickstartやAngular-CLIのような便利なツールがありますが、行われている設定や設定ファイルなどを理解しておくと、なにかあった時に役にたちますので。

作成したものはGitHubにあげてますのでご利用ください。

関連エントリー

anyenv+ndenvでプロジェクトごとにnode.jsのバージョンを切り替える webpackでJavaScriptライブラリを利用する npmとBrowserifyでjQueryプラグインを管理する babelifyで始めるES6 Babelで始めるES6入門