2018年10月にAngular7がリリースされましたが、ngUpgradeの使い方がよくわからなくてまだAngularJSな案件もあると思います。
今回は動くコードが欲しいのために、手っ取り早くngUpgradeを利用するための手引を紹介したいと思います。
§0 対象読者
- AngularJSもAngularも知ってるけど、ngUpgradeの使い方がよくわからない人
- AngularJS+Angularのハイブリッドアプリになかなか踏み込めない人
§1 準備
AngularJSやAngular、Angular CLI等の必要なフレームワーク/ライブラリは事前にnpmでインストール済みとします。
Angular、Angluar CLIともに最新バージョンで問題ありません。
※筆者はAngular v7.0.2で確認
§1.1 URLをHTML5モードにする
$locationProvider.html5Mode(true);
を設定してURLをHTML5パターンにしておきましょう。
§1.2 HTMLソース内からng-app
を削除
HTMLソース内のng-app
を削除します。
※もしng-strict-di
を利用しているようならそれも削除
§1.3 AngularJSのルートコンポーネントを設定する
AngularJSでルートコンポーネントがない場合、作成してください。
angular.module('app')
.component('appRoot', {
templateUrl: 'path/to/html' // or template: ``
controller: function () {}
});
§1.3 Angular用のディレクトリを作成する
src/ng2
などでよいと思います。
angular.json
,tsconfig.json
,polyfills.ts
,main.ts
などは普段お使いのプロジェクトから拝借してきましょう。
angular.json
内のエントリーポイントを作成したmain.ts
に設定します。
{
...省略
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "public/ng2",
"index": "src/index.html",
"main": "src/ng2/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
],
"styles": [
"src/styles.scss"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/js/tooltip.js",
"node_modules/bootstrap/js/popover.js",
"node_modules/bootstrap/js/dropdown.js",
"node_modules/bootstrap/js/tab.js",
"node_modules/bootstrap/js/modal.js"
]
},
...省略
§1.4 タスクランナー
§1.4.1 タスクランナーにwebpack以外(gulp/grunt)を利用しているバターン
アプリケーションファイルは最終的に一つのファイルに生成していると思います。
その生成先をsrc
内に変更します。
※私は src/ng2/ng1/application.min.js
として配置していました。
main.ts
から上記ファイル(例ではsrc/ng2/ng1/application.min.js
)をimportします。
§1.4.2 タスクランナーにwebpackを利用しているパターン
main.ts
からng1のエントリーファイルをimport
する
§2 Angular部分の設定
以降はAnguluar CLIの作法に則ったファイルだと考えてください。
§2.1 main.ts
ファイルの修正
main.ts
ファイルを修正します。
import 'ng2/ng1/application.min'; // webpackの場合はng1のエントリーファイルを指定
import 'ng2/ng1/downgrade'; // AngularのルートコンポーネントとなるMainComponentをAngularJSに登録しておく
import 'reflect-metadata';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { setAngularLib } from '@angular/upgrade/static';
import * as angular from 'angular';
import { enableProdMode } from '@angular/core';
import { environment } from 'environments/environment';
import { AppModule } from 'ng2/app.module';
if (environment.production) {
enableProdMode();
}
setAngularLib(angular);
platformBrowserDynamic().bootstrapModule(AppModule);
§2.2 AppModule
の修正
AppModuleを修正します。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, UrlHandlingStrategy, Router } from '@angular/router';
import { setUpLocationSync } from '@angular/router/upgrade';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppRoutes } from './app.routing';
import { Ng1Ng2UrlHandlingStrategy } from './routing-strategy';
import { MainComponent } from './main/main.component';
@NgModule({
imports: [
BrowserModule,
UpgradeModule,
RouterModule.forRoot(AppRoutes)
],
declarations: [
MainComponent
],
providers: [
{ provide: UrlHandlingStrategy, useClass: Ng1Ng2UrlHandlingStrategy },
],
entryComponents: [ MainComponent ]
})
export class AppModule {
constructor(
private upgrade: UpgradeModule,
private router: Router
) {}
ngDoBootstrap() {
// 'app'はng1でbootstrapに利用していたトップモジュール名に適宜変更しましょう
this.upgrade.bootstrap(document.body, ['app'], { strictDi: true });
setUpLocationSync(this.upgrade);
this.router.initialNavigation(); // これがないと初期表示がされないので注意
}
}
§2.3 Angular Routerのルーティング設定
Angular Routerのルーティング設定をします。
それぞれのModule1Moduleなどの定義はAngularの世界なため、本稿では割愛します。
普通にAngularの感覚で作成して大丈夫です。
※ redirectToはまだ実装してはいけません。
import { Routes } from '@angular/router';
export const AppRoutes: Routes = [
{
path: '',
children: [
{
path: '',
loadChildren: 'ng2/modules/dashboard/dashboard.module#DashboardModule'
},
{
path: 'module1',
loadChildren: 'ng2/modules/module1/module1.module#Module1Module'
},
{
path: 'module2',
loadChildren: 'ng2/modules/module2/module2.module#Module2Module'
}
]
}
];
§2.4 Aunglar Routerのルーティング戦略
Angular Routerのルーティング戦略を設定します。
import { UrlHandlingStrategy, UrlTree } from "@angular/router";
export class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
shouldProcessUrl(url: UrlTree) {
const str = url.toString();
// 以下にAngular化が終わったモジュールのURLを追記していきます
return str === '/' || // ダッシュボードのURL ※startsWithでやると全てがハンドリングされてしまうので注意
str.startsWith('/module1') || // URLが/module1で始まるモジュールはAngular化完了
str.startsWith('/module2'); // // URLが/module2で始まるモジュールはAngular化完了
}
extract(url) { return url; }
merge(url, whole) { return url; }
}
§2.5 Angularのルートコンポーネント設定
Angularのルートコンポーネントを定義します。
※中身は特に必要ありません
import { Component, OnInit } from "@angular/core";
@Component({
selector: 'app-main',
template: `<router-outlet></router-outlet>`
})
export class MainComponent implements OnInit {
constructor(
) { }
ngOnInit() {
}
}
§2.6 AngularルートコンポーネントをAngularJSへ登録
MainComponentをAngularJSに登録します。
下記ではng2/ng1/downgrade.ts
として新規にファイルを作成していますが、AngularJSをTypeScript化していた場合はAngularJSのほうで設定してしまっても構いません。
import * as angular from 'angular';
import { downgradeComponent } from '@angular/upgrade/static';
import { MainComponent } from 'ng2/main/main.component';
// AngularJSからもMainComponentを利用できるようにする
angular.module('app')
.directive('appMain', downgradeComponent({ component: MainComponent }));
以上でAngular側の設定完了です。
§3 AngularJSの修正
§3.1 Angular化したルーティングの設定
Angular化したルーティングでもAngularJSが識別できるように設定します。
angular.module('upgraded.route', ['ui-router'])
.config([
'$stateProvider',
function ($stateProvider) {
$stateProvider
.state({ name: 'dashboard', url: '/', template: '' }) // Angular化したルーティングはAngularJSでは何も表示しないようにする
.state({ name: 'module1', abstract: true })
.state({ name: 'module1.list', url: '/module1/list', template: '' })
.state({ name: 'module1.edit', url: '/module1/edit/:id', template: '' })
... 以後、Angular化完了したルーティングの分だけ追記していきます。
}
]);
§3.2 Angularのルートコンポーネントを設定
HTML内のメインビューとなっている<ui-view></ui-view>
(ngRouteの場合は<ng-view></ng-view>
)の直後に<app-main></app-main>
を追加
※app-rootはAngularJSのルートコンポーネント。適宜読み替えてください。
...省略
<app-root>
<ui-view></ui-view>
<app-main></app-main>
</app-root>
...省略
以上でAngularJSの設定は完了です。
あとはng build --watch
などでビルドしていけばうまくいくと思います。
※うまくいかない場合はご連絡ください。。。
§4 Angularへの移行
粛々と進めていきましょう。
Angular化が完了したコードはAngularJSから削除してルーティングの設定するのを忘れずに。
Angular化が進むにつれてLazy Loadの恩恵で初回読み込みファイルサイズが小さくなっていくのが気持ちいいです。
簡単そうな枝葉から移行してみて感覚を掴むとスムーズに進みます。
§5 まとめ
app-root
はAngularJSの世界、app-main
はAngularの世界になっています。
ui-viewではAngularJSのURLを処理、Angular化したURLなら何も表示しない。
router-outletでは、Angular化したURLを処理、それ以外は何も表示しない。
このことによって、完全にAngularJSの世界とAngularの世界を切り分けることができます。
AngularからAngularJSのDIを使いたい場合はInject angularjs service into AngularのAnswerを参考にするといいかもしれません。
AngularJSからAngularのサービスを使いたい場合は downgradeInjectable あたりが良さそうです。
(どちらも私は使ったことがありません)
ただしupgradeやdowngradeは、あくまでも自作のServiceとかProviderのみに限定して、ライブラリが用意しているものは素直に移行しましょう。
§6 付録
ngUpgradeを利用するにあたり、非常に参考になったスライドです。