LoginSignup
2

More than 5 years have passed since last update.

AngularJSとAngularのハイブリッドアプリ基本(AngularJS+ui-router+Angular+Angular Router+ngUpgrade+Lazy Load)

Last updated at Posted at 2018-11-07

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に設定します。

angular.jsonの例
{
...省略
      "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ファイルを修正します。

ng2/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を修正します。

ng2/app.module.ts
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はまだ実装してはいけません。

ng2/app.routing.ts
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のルーティング戦略を設定します。

ng2/routing-strategy.ts
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のルートコンポーネントを定義します。
※中身は特に必要ありません

ng2/main/main.component.ts
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のほうで設定してしまっても構いません。

ng2/ng1/downgrade.ts
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が識別できるように設定します。

ng1/upgraded.routing.js
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のルートコンポーネント。適宜読み替えてください。

index.html
...省略
  <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を利用するにあたり、非常に参考になったスライドです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2