Angular Info

Angular 2 RC.5がリリースされました

どうも、らこです。ついにAngular 2のRC.5がリリースされました。
2.0最終リリースに向けた最後の大規模アップデートということで、変更の量は凄まじいものがあります。
NgModule APIを始めとした既存APIの刷新は、一見バージョンアップ対応のハードルは高そうに見えますが、
RC.5に合わせた書き方に直していけば、これまでよりもわかりやすく直感的なAPIになっていることが感じられるはずです。

それでは重要な変更点をピックアップしていきます。

2.0.0-RC.5

NgModuleについて

新しいNgModule APIについては、 こちらの記事で簡単な解説をしているので、まずはそちらを読んでください。

破壊的変更

まずは破壊的変更についてまとめておきます。

bootstrappingに関する変更

一番大きな変更は、アプリケーションのbootstrappingに関するAPIの変更です。
NgModuleを前提とした新しいAPIが標準となり、今までのコンポーネントベースのbootstrapingは非推奨になりました。

アプリケーションを実装するにあたって、まずはモジュールを作ることになります。モジュールは次のように@NgModuleデコレータを使います。

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

@NgModule({
  declarations: […], // モジュールに読み込むディレクティブ、コンポーネント、パイプ
  imports: [BrowserModule], // 依存するモジュール
  providers: […], // DIプロバイダ
  boostrap: [MainComponent], // アプリケーションのエントリポイントになるコンポーネント
})
class MyAppModule {}

こうして宣言したモジュールは、次のようにbootstrappingします。

import {platformBrowserDynamic} from ‘@angular/platform-browser-dynamic’;

platformBrowserDynamic().bootstrapModule(MyAppModule);

AoT(Ahead of Time)コンパイルを利用する場合は、次のようになります

import {platformBrowser} from ‘@angular/platform-browser’;

platformBrowser().bootstrapModuleFactory(MyAppModuleNgFactory);

Web Worker向けAPIの変更

これまでWeb Workerでアプリケーションを起動するにはbootstrapWorkerAppを使っていましたが、
WorkerAppModuleworkerAppPlatform()を使って、モジュールをbootstrapすることになります。

bootstrapWorkerUiについても、WorkerUiModuleをインポートして、workerUiPlatform()でbootstrapします。

Server向けAPIの変更

serverBootstrapは廃止され、アプリケーションのモジュールをserverDynamicPlatform()でbootstrapすることになります

Compilerに関する変更

コンポーネントコンパイラは常にCompilerクラスでInjectするようになりました。
RuntimeCompilerクラスやOfflineCompilerクラスを直接Injectすることはできません。

ApplicationRefに関する変更

アプリケーションの主体がモジュールに移行したことから、ApplicationRef関連のAPIの多くが非推奨になりました。

ApplicationRef#waitForAsyncInitializersの廃止

アプリケーションの初期化が完了したことを受け取るためのAPIだったApplicationRef#waitForAsyncInitializersが廃止されました。
代わりにAppInitStatus#donePromise(): Promiseか、AppInitStatus#done: booleanを使います。
AppInitStatusは新しく導入されたクラスで、Injection経由でインスタンスを取得できます。

ApplicationRef.registerBootstrapListenerの廃止

bootstrapが完了した後に呼び出されるイベントリスナーを登録するためのAPIだったApplicationRef.registerBootstrapListenerが廃止されました。
代わりに、APP_BOOTSTRAP_LISTENERトークンを使って、multi providerを設定します。

[
    {
        provide: APP_BOOTSTRAP_LISTENER,
        multi: true,
        useValue: (cmp: ComponentRef) => {
            // bootstrapされたコンポーネントのComponentRefが渡される
        }
    }
]

ApplicationRef#disposeの廃止

アプリケーションを破棄するApplicationRef#disposeが廃止されました。
代わりにモジュールを破棄するNgModuleRef#destroyを使います。

AplicationRef#registerDisposeListenerの廃止

アプリケーションが破棄されたイベントを受け取るためのAPIが廃止されました。
代わりにモジュールのクラスでngOnDestroyライフサイクルメソッドを実装するか、
NgModuleRefクラスをInjectして、NgModuleRef#onDestroyメソッドでイベントリスナーを登録します。

ApplicationRef#runの廃止

ApplicationRef#runは廃止され、代わりにNgZone#runを直接使うようになります。

ApplicationRef#injectorの廃止

ApplicationRef#injectorは廃止され、代わりにInjectorを直接Injectするか、NgModuleRef#injectorを使います。

ApplicationRef#zoneの廃止

ApplicationRef#zoneは廃止され、NgZoneを直接使います。

disposePlatformの廃止

disposePlatformdestroyPlatformに名前が変わりました。
全体としてdisposeからdestroyに統一するためです。

同様に、PlatformRef#dipose()からPlatformRef#destroy()に、
PlatformRef#registerDisposeListenerからPlatformRef.onDestroyに、
PlaformRef#diposedからPlatformRef#destroyedに、それぞれ改名されました。

テンプレートスキーマに関する挙動の変更

デフォルトでは、テンプレート中の未知の要素に対するデータバインディングはエラーを起こすようになっています。
ただし、CUSTOM_ELEMENTS_SCHEMAを有効にすると、要素名に-を含む場合はその要素をCustom Elementsとして扱い、既知の要素として扱います。

CUSTOM_ELEMENTS_SCHEMA@NgModuleschemasに設定します。

@NgModule({
    declarations: [MyComponentThatUsesAWebComponent],
    imports: [BrowserModule],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    boostrap:  [MyComponentThatUsesAWebComponent],
})
export class MyAppModule{}

coreLoadAndBootstrap/coreBootstrapの廃止

coreLoadAndBootstrapcoreBootstrapは廃止されました。代わりにはbootstrapModulebootstrapModuleFactoryを使います。

RouteConfigとdeclarationsに関する規定

ルート設定に含まれるすべてのコンポーネントは、モジュールのdeclarationsに含まれている必要があります。
これはJITコンパイル、AoTコンパイル、遅延ロードによらず常に必要です。

テスティングAPIに関する変更

@angular/core/testingをはじめとするテスティングAPIも、NgModuleを前提としたAPIに刷新されました。

TestInjectorの廃止とTestBedの導入

これまでユニットテスト中のInjectionを管理していたTestInjectorが廃止され、
代わりにユニットテスト用のモジュールを管理するTestBed APIが導入されました。

withProvidersTestBed.withModuleに、addProvidersTestBed.configureTestingModuleに変更されます。
TestComponentBuilderTestBed.createComponentに変更されます。

Example:

import {TestBed} from '@angular/core/testing';

describe('TestComponent', () => {
  beforeEach(() => {
    // テスト用モジュールのセットアップ
    TestBed.configureTestingModule({
      declarations: [TestComponent]
    });
  });

  it('component test', async(() => {
    // Component設定の上書き
    TestBed.overrideComponent(TestComponent, {set: {
      template: '<p>Content</p>'
    }});
    // コンポーネントのコンパイル
    TestBed.compileComponents().then(() => {
      // コンポーネントインスタンスの作成 
      var fixture = TestBed.createComponent(TestComponent);
      fixture.detectChanges();
      var compiled = fixture.debugElement.nativeElement;

      expect(compiled).toContainText('Content');
    });
  }));
});

テスト環境のセットアップを行うsetBaseTestProvidersも、TestBed.initTestEnvironment()に変更されます。

Before:

import {setBaseTestProviders} from '@angular/core/testing';
import {
    TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
    TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
} from '@angular/platform-browser-dynamic/testing';

setBaseTestProviders(
    TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
    TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
);

After:

import {TestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';

TestBed.initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting()
);

ngModelの挙動の変更と、ユニットテストへの影響

ngModelは常に非同期的な挙動を取るようになりました。
つまり、ユニットテスト時にはComponentFixture#detectChangesの呼び出しではなく、
ComponentFixture#whenStableを使って非同期的に変更の反映を待つ必要があります。

Before:

let fixture = TestBed.createComponent(InputComp);
fixture.detectChanges();

let inputBox = <HTMLInputElement> fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputBox.value).toEqual('Original Name');

After:

let fixture = TedBed.createComponent(InputComp);
fixture.whenStable().then(() => {
    let inputBox = <HTMLInputElement> fixture.debugElement.query(By.css('input')).nativeElement;
    expect(inputBox.value).toEqual('Original Name');
});

Formsのモジュール化

@angular/formsを使用するためのAPIが変わります。今後は各フォームAPI用のモジュールをインポートします。

Before:

import {disableDeprecatedForms, provideForms} from @angular/forms;

bootstrap(App, [
  disableDeprecatedForms(),
  provideForms()
]);

After:

import {DeprecatedFormsModule, FormsModule, ReactiveFormsModule} from @angular/common;

@NgModule({
  declarations: [MyComponent],
  imports: [BrowserModule, DeprecatedFormsModule],
  boostrap:  [MyComponent],
})
export class MyAppModule{}

PLATFORM_PIPESPLATFORM_DIRECTIVESの廃止

どちらもNgModule#declarationsを使うようになりました

Routerの挙動の変更

クエリパラメータやフラグメントなど、URLに付随する要素は、デフォルトでは保持されなくなりました。
ナビゲーションの際に保持したい場合は、preserveQueryParams/preserveFragmentsオプションをtrueにします。

router.navigate(["/"], { preserveQueryParams: true });

routerLinkの場合は、[preserveQueryParams]として設定します。

<a routerLink="/" [preserveQueryParams]="true">top</a>

アニメーションのテンプレートシンタックスの変更

アニメーションのトリガーの式は@propという書き方でしたが、[@prop]に変更されました。

// 非推奨
<div @trigger="expression"></div>

// 新しい書き方
<div [@trigger]="expression"></div>

ngUpgrade関連の変更

ngUpgradeもモジュール対応しました。

Before:

let upgradeAdapter = new UpgradeAdapter();
upgradeAdapter.addProviders([myProvidersArray);

After:

@NgModule({
  providers: myProvidersArray
})
class MyModule {}

let upgradeAdapter = new UpgradeAdapter(MyModule);

その他の変更

破壊的変更以外にも要注意の変更はいくつもあるので、それぞれ見ていきます。

core

ngForの挙動の修正

e18626b

ngForの挙動が修正され、必要なときだけ要素の追加・移動・削除を行うようになりました。
この変更はアニメーションに影響するものです。

selectorを持たないコンポーネントを許容するように修正

9b39e49

これまではselectorが設定されていないコンポーネントを動的に読み込んだとき、DOM上は<undefined>として展開されていましたが、
今後は<ng-component>として展開されるようになりました。

compiler

ngIfの右辺でパイプが使えるように修正

8efbcc9

*ngIfの右辺の式でパイプが使えるようになりました。

Safe Operator(?.)の評価の挙動を修正

4ec2a30

a?.b.cという式についてaがnullあるいはundefinedだった場合、これまではaをnullと評価したあとにbの評価に進んでしまい、
a?.b?.cと書かないとエラーになっていました。
この修正により、aの時点でSafe Operatorが働いた場合はその先の評価を行わなずに全体をnullとするように修正されました。

common

CurrencyPipeの規定の挙動の変更

d455942

CurrencyPipeの各パラメータの規定値が、Intl APIのデフォルトを使うようになりました。

NumberPipeがstring型を許容するようになった

f3dd91e

NumberPipeが文字列型の数値も許容するようになりました。

http

HTTPヘッダの処理をcase-insensitiveに修正

7f64782

これまでケースセンシティブになっていたのが修正されました

content-typeヘッダの上書きをサポート

bdb5912

bodyに渡されたオブジェクトの型から自動的に判別されるcontent-typeは、
headersに明示的に指定されたもので上書きできるようになりました。

arraybuffer型とblob型のレスポンスをサポート

1266460, 76b8a49

Response#arrayBuffer()Response#blob()がサポートされました。

OPTIONSメソッドをサポート

0bd97ec

HTTPのOPTIONSメソッドのためのHttp#options()が追加されました。

router

routerについては個別のチェンジログがあります

Router CHANGELOG

RouterModuleの導入

Routerもモジュール化され、RouterModuleをインポートするようになります。

@NgModule({
    imports: [RouterModule.forRoot(appRoutes, {enableTracing: true})]
})
class MyAppModule {}

クエリパラメータの値が空の時の挙動を修正

0d6cc17

値が空(?key=)のクエリパラメータを含むURLで初回のルーティングを行うと、値にtrueがセットされてしまうバグを修正しました。

canActivateChild/canLoadの追加

ガードの種類に、すべての子ルートのアクティベーションをチェックするcanActivateChildと、
遅延ロードが可能かどうかをチェックするcanLoadが追加されました

RouterOutletのイベントを追加

<router-outlet>要素からactivateイベントとdeactivateイベントが発火されるようになりました。

GuardとResolverがPromiseをサポート

各種GaurdとResolverのインターフェースで、戻り値としてPromiseが使えるようになりました。
Observableと同様に、Promiseを返した場合は完了するまでナビゲーションが待機されます。

ActivateRouterouteConfigを返せるようになった

現在アクティブなルートの設定をActivateRouteから取得できます。

queryParamsfragmentActivateRouteから取得するようになった

これまでRouter#routerStateから取得していたクエリパラメータやフラグメントはActivateRouteから取得するようになりました

forms

NgForm#resetの追加

da8eb9f

フォームを初期化するNgForm#reset()が追加されました

multipleなselect要素でchangeイベントを使うように修正

3cbded6

これまでinputイベントで処理していたバグが修正されました。

フォーム内のコントロールを簡単に取得するためのAPIを追加

8d44999

フォーム内のコントロールを名前で取得できるget()メソッドが追加されました。

@Component({
  selector: 'my-app',
  template: `
    <div>
      <form #f="ngForm">
        <input name="a" ngModel>
      </form>
      <button (click)="checkForm(f)">Check Form</button>
    </div>
  `,
})
export class App {
  checkForm(f: NgForm) {
    f.form.get("a") // FormControl
  }
}

コントロールにinvalid/pendingプロパティを追加

e0eea6c

コントロールの状態を取得するプロパティが追加されました。

animation

Component#hostプロパティでのアニメーショントリガーの設定をサポート

806a254

@Componenthostプロパティでアニメーションのトリガー設定ができるようになりました。
この結果として、Routerによるナビゲーションにアニメーションを付与することができるようになります。
[@routeAnimation]をtrueに設定しておくと、routeAnimationトリガーで、表示(void => *)と、離脱(* => void)のアニメーションを記述できます。

@Component({
  selector: 'home',
  template: `Home`,
   host: {
     '[@routeAnimation]': 'true',
   },
  animations: [
    trigger('routeAnimation', [
      state('*', style({transform: 'translateX(0)', opacity: 1})),
      transition('void => *', [
        style({transform: 'translateX(-100%)', opacity: 0}),
        animate(1000)
      ]),
      transition('* => void', animate(1000, style({transform: 'translateX(100%)', opacity: 0})))
    ])
  ]
})
class HomeCmp {}

実際に動くサンプルはこちらです


RC.5の変更点のまとめは以上です。かなり抜粋しましたがそれでもこの量です。
今回のアップデートではAoTコンパイルに関する情報も出てきていますが、この記事では扱いません。
後日AoTコンパイルの手順については改めて書こうと思います。

それではFinalリリースまであと少し、頑張っていきましょう。よい夏を!