どうも、らこです。ついにAngular 2のRC.5がリリースされました。
2.0最終リリースに向けた最後の大規模アップデートということで、変更の量は凄まじいものがあります。
NgModule APIを始めとした既存APIの刷新は、一見バージョンアップ対応のハードルは高そうに見えますが、
RC.5に合わせた書き方に直していけば、これまでよりもわかりやすく直感的なAPIになっていることが感じられるはずです。
それでは重要な変更点をピックアップしていきます。
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を使っていましたが、
WorkerAppModuleとworkerAppPlatform()を使って、モジュールを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の廃止
disposePlatformはdestroyPlatformに名前が変わりました。
全体としてdisposeからdestroyに統一するためです。
同様に、PlatformRef#dipose()からPlatformRef#destroy()に、
PlatformRef#registerDisposeListenerからPlatformRef.onDestroyに、
PlaformRef#diposedからPlatformRef#destroyedに、それぞれ改名されました。
テンプレートスキーマに関する挙動の変更
デフォルトでは、テンプレート中の未知の要素に対するデータバインディングはエラーを起こすようになっています。
ただし、CUSTOM_ELEMENTS_SCHEMAを有効にすると、要素名に-を含む場合はその要素をCustom Elementsとして扱い、既知の要素として扱います。
CUSTOM_ELEMENTS_SCHEMAは@NgModuleのschemasに設定します。
@NgModule({
declarations: [MyComponentThatUsesAWebComponent],
imports: [BrowserModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
boostrap: [MyComponentThatUsesAWebComponent],
})
export class MyAppModule{}
coreLoadAndBootstrap/coreBootstrapの廃止
coreLoadAndBootstrapとcoreBootstrapは廃止されました。代わりにはbootstrapModuleかbootstrapModuleFactoryを使います。
RouteConfigとdeclarationsに関する規定
ルート設定に含まれるすべてのコンポーネントは、モジュールのdeclarationsに含まれている必要があります。
これはJITコンパイル、AoTコンパイル、遅延ロードによらず常に必要です。
テスティングAPIに関する変更
@angular/core/testingをはじめとするテスティングAPIも、NgModuleを前提としたAPIに刷新されました。
TestInjectorの廃止とTestBedの導入
これまでユニットテスト中のInjectionを管理していたTestInjectorが廃止され、
代わりにユニットテスト用のモジュールを管理するTestBed APIが導入されました。
withProvidersはTestBed.withModuleに、addProvidersはTestBed.configureTestingModuleに変更されます。
TestComponentBuilderもTestBed.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_PIPESとPLATFORM_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の挙動の修正
ngForの挙動が修正され、必要なときだけ要素の追加・移動・削除を行うようになりました。
この変更はアニメーションに影響するものです。
selectorを持たないコンポーネントを許容するように修正
これまではselectorが設定されていないコンポーネントを動的に読み込んだとき、DOM上は<undefined>として展開されていましたが、
今後は<ng-component>として展開されるようになりました。
compiler
ngIfの右辺でパイプが使えるように修正
*ngIfの右辺の式でパイプが使えるようになりました。
Safe Operator(?.)の評価の挙動を修正
a?.b.cという式についてaがnullあるいはundefinedだった場合、これまではaをnullと評価したあとにbの評価に進んでしまい、
a?.b?.cと書かないとエラーになっていました。
この修正により、aの時点でSafe Operatorが働いた場合はその先の評価を行わなずに全体をnullとするように修正されました。
common
CurrencyPipeの規定の挙動の変更
CurrencyPipeの各パラメータの規定値が、Intl APIのデフォルトを使うようになりました。
NumberPipeがstring型を許容するようになった
NumberPipeが文字列型の数値も許容するようになりました。
http
HTTPヘッダの処理をcase-insensitiveに修正
これまでケースセンシティブになっていたのが修正されました
content-typeヘッダの上書きをサポート
bodyに渡されたオブジェクトの型から自動的に判別されるcontent-typeは、
headersに明示的に指定されたもので上書きできるようになりました。
arraybuffer型とblob型のレスポンスをサポート
Response#arrayBuffer()とResponse#blob()がサポートされました。
OPTIONSメソッドをサポート
HTTPのOPTIONSメソッドのためのHttp#options()が追加されました。
router
routerについては個別のチェンジログがあります
RouterModuleの導入
Routerもモジュール化され、RouterModuleをインポートするようになります。
@NgModule({
imports: [RouterModule.forRoot(appRoutes, {enableTracing: true})]
})
class MyAppModule {}
クエリパラメータの値が空の時の挙動を修正
値が空(?key=)のクエリパラメータを含むURLで初回のルーティングを行うと、値にtrueがセットされてしまうバグを修正しました。
canActivateChild/canLoadの追加
ガードの種類に、すべての子ルートのアクティベーションをチェックするcanActivateChildと、
遅延ロードが可能かどうかをチェックするcanLoadが追加されました
RouterOutletのイベントを追加
<router-outlet>要素からactivateイベントとdeactivateイベントが発火されるようになりました。
GuardとResolverがPromiseをサポート
各種GaurdとResolverのインターフェースで、戻り値としてPromiseが使えるようになりました。
Observableと同様に、Promiseを返した場合は完了するまでナビゲーションが待機されます。
ActivateRouteがrouteConfigを返せるようになった
現在アクティブなルートの設定をActivateRouteから取得できます。
queryParamsやfragmentがActivateRouteから取得するようになった
これまでRouter#routerStateから取得していたクエリパラメータやフラグメントはActivateRouteから取得するようになりました
forms
NgForm#resetの追加
フォームを初期化するNgForm#reset()が追加されました
multipleなselect要素でchangeイベントを使うように修正
これまでinputイベントで処理していたバグが修正されました。
フォーム内のコントロールを簡単に取得するためのAPIを追加
フォーム内のコントロールを名前で取得できる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プロパティを追加
コントロールの状態を取得するプロパティが追加されました。
animation
Component#hostプロパティでのアニメーショントリガーの設定をサポート
@Componentのhostプロパティでアニメーションのトリガー設定ができるようになりました。
この結果として、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リリースまであと少し、頑張っていきましょう。よい夏を!