Slide 1

Slide 1 text

ngUpgradeと移植戦略 Mar 21, 2016 / ng-japan armorik83

Slide 2

Slide 2 text

はちさん 奥野 賢太郎 @armorik83 ng-kyoto代表 ChatWork株式会社

Slide 3

Slide 3 text

Qiita

Slide 4

Slide 4 text

ng-kyoto 4/10 渋谷ヒカリエ by teratail / ng-kyoto, ng-japan 4/16 サイボウズ大阪梅田オフィス / ng-kyoto, GDG神戸 ng-kyoto Angular 2ハンズオン勉強会 東西合同開催!!

Slide 5

Slide 5 text

お話しすること • なぜ移行戦略ではなく移植戦略なのか • 移植の前に現在地を知る • 移植の複数のフェーズ • Angular 1のこれって2ではどう書くの?

Slide 6

Slide 6 text

なぜ移行戦略ではなく
 移植戦略なのか

Slide 7

Slide 7 text

その前に前提 • 業務としてAngular 2化を進めている上での知見 • 1と2は全く互換性のない同名の別フレームワーク
 というのは誤解 • UPGRADING FROM 1.X
 https://angular.io/docs/ts/latest/guide/upgrade.html • Angular 1から2へは移行可能

Slide 8

Slide 8 text

なのになぜ移植と呼ぶか • すべての手順を公式サイトの通りに進めるのは煩雑 • 世界中のAngularのコーディング・スタイルはバラバラ • 「ドキュメント通りに移行しなければ」と悩むより
 Angular 2流儀を覚えてイチから書いたほうが
 むしろ早い可能性もある

Slide 9

Slide 9 text

とはいえ移植はしんどい! • これを全て移植するのか…と憂鬱になる • できることなら移行したい • 楽に移行したい

Slide 10

Slide 10 text

段階的移植法 • 段階的移植法(armorik83命名) • 公式サイトの移行手順は活かしながら
 ある程度は移植(書き直し)する • 移行を複数のフェーズで捉える • 少しずつ移植を進めて
 最終的には全てがAngular 2な状態へ

Slide 11

Slide 11 text

移植の前に現在地を知る

Slide 12

Slide 12 text

現在地はどこだ TypeScript Angular 2 ES2015 ES5 Coffee Script AngularJS 1.4, 1.5 1.3 1.2

Slide 13

Slide 13 text

TypeScript Angular 2 ES2015 ES5 Coffee Script AngularJS 1.4, 1.5 1.3 1.2 この距離で
 移植難度が異なる 現在地はどこだ

Slide 14

Slide 14 text

言語をどうするか • CoffeeScriptが最も遠く
 TypeScriptが最も近い • ES5, ES2015の場合
 「--allowJsを付けてJS, TSを混ぜてコンパイル」でも
 動くので段階的TypeScript化でなんとかなる • Angular 2はTypeScript専用ではないので
 ES5でも扱える

Slide 15

Slide 15 text

Angular 1.xのバージョン • 最新にするに越したことはない • 1.2を使っている場合
 残念ながら移行への道のりは険しい • 一旦1.5まで移行してから2への段階的移植を
 試みたほうがよい(もしくは諦める) • 1.4を使っている場合
 1.4 → 1.5は楽なのでまず1.5にして2化に備えておく

Slide 16

Slide 16 text

いまAngular 1をどう書くか • 手前味噌ですがAngularJSモダンプラクティスは
 現在も有効
 http://qiita.com/armorik83/items/5542daed0c408cb9f605 • AngularJS老化チェックの記事で現在のプロダクトの 老化度を測り若返らせる(1.5に寄せていく)
 http://qiita.com/laco0416/items/edfa917583af4593ad6c

Slide 17

Slide 17 text

1.5 Componentにすべきか • Angular 1.5から追加された angular.module().component() • 1.4までのDirectiveは1.5のComponentに
 一旦移行してからAngular 2化すべきか?

Slide 18

Slide 18 text

1.5でのDirectiveの扱い • 現在のプロダクトで無理にDirectiveとComponentを
 両方を混ぜる必要はない(好みの話) • 無理にすべて移行する必要も無い • ただしDirectiveを構造的に細分化しているなら不要だが
 そこの考慮が無い場合一旦Componentにしていくとよい • $scopeを多用している場合、先にComponent化すべき • Angular 1.5を今から新しく始めるならば
 最初からComponentを使うべき

Slide 19

Slide 19 text

移植の複数のフェーズ

Slide 20

Slide 20 text

移植戦略マップ TypeScript Angular 2 ES2015 ES5 Coffee Script AngularJS 1.4, 1.5 1.3 1.2 この箇所を細分化する

Slide 21

Slide 21 text

複数のフェーズ 1. 準備 2. ng2 Component作成 3. ng1 ServiceのUpgrade 4. ng2 Componentへの機能移植 5. ng1 Serviceのng2 Service化 6. Router周り、bootstrap周りの移植

Slide 22

Slide 22 text

1. 準備 • 幾つものUIパーツで構成されるWebアプリケーションを想定 • チーム内で合意を得る、稟議を通す
 稟議の通し方は様々だが、パフォーマンス改善は説得力ある • キャッチアップの社内勉強会、外部の勉強会に参加 • branchを分ける、依存関係の修正
 ビルド環境の調整など足回りを組み立てる • 一度Angular 2チュートリアルでミニアプリ開発を
 試しておくと吉

Slide 23

Slide 23 text

2. ng2 Component作成 app-main app-nav app-contents app-nav-items app-contents-body ng1 Directiveと同要素名でng2 Componentを作成 とにかくガワを作る まだマウスやウインドウのイベント・ハンドリング移植はしない ng-ifやng-repeatは、ここで*ngIfや*ngForに書き直す app-main app-nav app-contents app-nav-items app-contents-body app-root

Slide 24

Slide 24 text

2. ng2 Component作成 ルートだけはng1 Directiveで
 そこに連なる要素をすべてng2 Componentにする ルートに置くng2 Componentを
 upgradeAdapter.downgradeNg2Component()でng1でも利用できるよう変換 app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root 変換 ng1の世界 ng2の世界

Slide 25

Slide 25 text

2. ng2 Component作成 テンプレートからng-appを除去 angular.bootstrap()形式にリファクタリングしておく さらにupgradeAdapter.bootstrap()に書き直すとng1内でng2を扱える app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root 変換 ng1の世界 ng2の世界

Slide 26

Slide 26 text

3. ng1 ServiceのUpgrade • ng1 ServiceをupgradeAdapter.upgradeNg1Provider()に通して ng2で利用可能な状態にする • ng2 Componentにng1 ServiceをDIする • コードの書き方を詳細に話すと時間がないので割愛 • UPGRADING FROM 1.X
 https://angular.io/docs/ts/latest/guide/upgrade.html

Slide 27

Slide 27 text

4. ng2 Componentへの機能移植 • 機能をng1 Directiveからng2 Componentへ移植していく • コピペでいけるものもあるが、書き直す必要がある箇所もある • ng1 Directiveにロジックをベタッと書かず
 あらかじめng1 Serviceから利用する形にしておくと
 だいぶこのフェーズは楽になる • ng2 ServiceはupgradeAdapter.bootstrap()で起動する
 前にupgradeAdapter.addProvider()で登録しておく

Slide 28

Slide 28 text

ng1 Serviceをng2 Serviceに移植する 移植作業中はng2 Componentからng1 Serviceを使っておく 5. ng1 Serviceのng2 Service化 app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root Ng1APIService Ng1StorageService Ng2APIService 裏で移植作業 Ng2StorageService

Slide 29

Slide 29 text

必要な移植作業が終わったらng1 Serviceはお役御免となる app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root Ng1APIService Ng1StorageService Ng2APIService 移植できた Ng2StorageService 5. ng1 Serviceのng2 Service化

Slide 30

Slide 30 text

ng2 Service化できたらng1 Serviceをng2 Serviceに付け替える テストで切り替え前後の挙動に変化が無いか担保できるとよし 最悪目視・手動操作で確認 app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root Ng2APIService ng2化 Ng2StorageService 5. ng1 Serviceのng2 Service化

Slide 31

Slide 31 text

app-main app-nav app-contents app-nav-items app-contents-body app-root 6. Router周り、bootstrap周りの移植 ルート要素もng2化、アダプタを用いた接合部分もアダプタを外す ui-routerやbootstrap周りもAngular 2に移植 おそらくけっこう大変なフェーズ(うちもまだ辿り着いていない) app-main-ng2 app-nav app-contents app-nav-items app-contents-body app-root

Slide 32

Slide 32 text

Angular 1のこれって
 2ではどう書くの?

Slide 33

Slide 33 text

Filterの使用 ng1 {{movie.title | uppercase}} ng2 {{movie.title | uppercase}} Angular 2ではFilterのことをPipeと呼ぶ Angular 1 to 2 Quick Refを読めば対応が表にまとめられているので必読 https://angular.io/docs/ts/latest/cookbook/a1-a2-quick-reference.html

Slide 34

Slide 34 text

Filterの定義 ng1 angular.module().filter("reverse", function(){}); ng2 @Pipe({name: "reverse"}) export class FilterPipe implements PipeTransform { transform(value: string): string { return // } } Pipe Annotationを付けたclassにtransform()メソッドを実装する

Slide 35

Slide 35 text

Serviceの定義 ng1 angular.module().service("Service", function(){}); ng2 @Injectable() export class Service { // } bootstrap(RootComponent, [Service]); Injectable Annotationを付けたclassをbootstrap第二引数に渡す または、DIしたいComponentのprovidersプロパティに渡す

Slide 36

Slide 36 text

イベントのハンドリング ng1 $element[0].addEventListener("mouseenter", function(){ this.color = "#0000ff"; $scope.$apply(); }); ng2 @HostListener("mouseenter", ["$event"]) onMouseenter($event: MouseEvent) { this.color = "#0000ff"; } 自身のイベントをハンドリングするなら @HostListener()をメソッドに付ける

Slide 37

Slide 37 text

$apply ng1 $scope.$apply(); ng2 ChangeDetectorRef.detectChanges() Zonesのお陰で基本的に手動での変更検知発火は不要 どうしても手動で再bindを走らせたいときは ChangeDetectorRefをDIし、detectChanges()を呼ぶ

Slide 38

Slide 38 text

$watch ng1 $scope.$watch("name", function(newVal, oldVal) { // }); ng2 ngOnChanges(changes) { console.log(changes["name"].currentValue); } ngOnChanges()にて可能

Slide 39

Slide 39 text

$watchCollection ng1 $scope.$watchCollection("obj", function(newObj, oldObj) { // }); ng2 ngDoCheck() { const changes = this.differ.diff(this.list); if (changes) { // } } 配列ならばIterableDiffers、オブジェクトならばKeyValueDiffersを用いて ngDoCheck()内で差分を検出することで可能 準備が面倒な上に処理負荷が大きめなので極力ngOnChanges()で済ませたい

Slide 40

Slide 40 text

One-time Binding ng1 {{::param}} ng2 @Component({ selector: "my-component", changeDetection: ChangeDetectionStrategy.OnPush, template: `{{param}}` }) ChangeDetectionStrategy.OnPushを指定する その中で手動で再bindしたいときは ChangeDetectorRef.markForCheck()を呼ぶ

Slide 41

Slide 41 text

compile, link, destroy • ng1 DirectiveのlinkはngOnInit(), ngAfterViewInit()などの
 Lifecycle Hooksで代替可能
 https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html • $scope.$on("$destroy")はLifecycle HooksのngOnDestroy() で代替可能 • ng1 Directiveのcompileプロパティで行っていたフックは
 ng2 Componentでは代替不可 • ngOnInit()で近いことはできる
 jQueryを使って別要素を生成していたりすると移植困難

Slide 42

Slide 42 text

まとめ

Slide 43

Slide 43 text

なんとかなる

Slide 44

Slide 44 text

ありがとうございました ngUpgradeと移植戦略