最後のガイドでは、AngularJSからAngularへのアップグレードを開始するために必要なすべてをインストールする方法について説明しました。 また、コンポーネントを書き直してダウングレードする方法についても説明しました。

このガイドでは、ngUpgradeプロジェクトでサービスを操作します。 具体的には、次のことを行います。

  • AngularJSサービスをAngularに書き直します

  • オブザーバブルを約束に変換する

  • サービスをダウングレードして、AngularJSコードで引き続き機能するようにします

  • 約束を観察可能なものに変換する

私たちの出発点

このサンプルプロジェクトをGitHubで複製またはフォークするには、少し時間がかかります(実行することを忘れないでください) npm install 両方で publicserver フォルダ)。 このコミットをチェックアウトして、開始点を確認してください。

  1. git checkout 083ee533d44c05db003413186fbef41f76466976

ngUpgradeを介して作業するために使用できる注文システムプロジェクトがあります。 コンポーネントアーキテクチャ、TypeScript、およびWebpack(開発と本番の両方のビルドを含む)を使用しています。 また、AngularとngUpgradeをセットアップしてブートストラップし、ホームコンポーネントをAngularに書き直しました。

(そのいずれかに迷った場合は、包括的なビデオコース AngularJSのアップグレードですべてをカバーします。)

1つの簡単なメモ:AngularとRxJSでは状況が急速に変化します。 Angular 4.3以降または5以降を使用している場合は、サンプルプロジェクトと比較して、ここでいくつかのわずかな違いが見られます。 サンプルプロジェクトは Http のようなHTTP呼び出しのサービスで GETPOST. 新しいものを使用します HttpClient これは、このチュートリアルの目的に必要な機能を備えたバージョン4.3以降で追加されました…RxJSは、バージョン5.5でもインポート方法にいくつかの変更を加えたため、ここではその新しいスタイルを使用します。

AngularJSサービスの書き換え

ngUpgradeを実行するときは、一度に1つのルートを選択し、下から上に向かって作業するのが賢明です。 アプリを壊すことを心配せずに、AngularとAngularJSを並べて実行し続けることができます。

前回のガイドでホームルートを実行したので、これで開始する準備が整いました。 customers ルート。 まず、CustomerServiceを書き直してダウングレードし、AngularJSコンポーネントで利用できるようにします。 次に、サービスでオブザーバブルとプロミスの両方を使用する方法を見ていきます。これにより、移行プロセスで最適なものを自分で選択できます。

NgModuleへのHttpClientの追加

CustomerServiceを書き直す前に、HTTP呼び出しを行うために、AngularのHttpClientModuleをアプリのNgModule( app.module.ts )に明示的にインポートする必要があります。 これは、すべてがデフォルトで含まれていたAngularJSとは異なります。 Angularでは、Angularのどの部分を使用するかを明示する必要があります。 最初は不便に思えるかもしれませんが、未使用のコードを自動的にインポートしないことでアプリケーションのフットプリントを削減できるため、これはすばらしいことです。

したがって、3行目以降は、次のようにインポートします。

import { HttpClientModule } from '@angular/common/http';

次に、そのモジュールを imports 12行目のUpgradeModuleの後の配列:


//app.module.ts

@NgModule({

    imports: [

        BrowserModule,

        UpgradeModule,

        HttpClientModule

    ],

    declarations: [

        HomeComponent

    ],

    entryComponents: [

        HomeComponent

    ]

})

これで、アプリケーション全体でHttpClientModuleを使用できるようになりました。 インポートする必要があるのは1回だけで、アプリケーション全体の残りのすべてのサービスに使用できます。

カスタマーサービスの書き直し

AngularアプリモジュールにHttpClientModuleが追加されたので、AngularでCustomerServiceを書き直す準備ができました。 次に、ダウングレードして、AngularJSコンポーネントとAngularコンポーネントで引き続き使用できるようにします。

最初に行うことは、名前を変更することです customerService.ts にファイルする customer.service.ts 現在の命名規則に従うようにします。

それでは、ファイルを開きましょう。 ES2015クラスをすでに使用していることがわかります。


//customer.service.ts

class CustomerService{

    $http: any;

    constructor($http) {

        this.$http = $http;

    }


    getCustomers(){

        return this.$http.get('/api/customers')

            .then((response) => response.data);

    }


    getCustomer(id){

        return this.$http.get(`/api/customers/${id}`)

            .then((response) => response.data);

    }


    postCustomer(customer){

        return this.$http.post('/api/customers', customer)

            .then((data) => data);

    }

}


CustomerService.$inject = ['$http'];

export default CustomerService;

Angular 2+サービスはエクスポートするクラスですが、 Injectable() 注釈。 工場、サービス、プロバイダー、そしてそれぞれを作成する方法を思い出そうとする時代は終わりました。 Angularでは、サービスはサービスであり、注入可能なアノテーションを持つエクスポートされたクラスにすぎません。 それは大きな安堵ではありませんか?

コードの準備

最初にできることは、このファイルの最後の2行を削除することです。 AngularJSはもう必要ありません $inject 配列、および使用する代わりに export default、追加します export クラス宣言の前のキーワード:

export CustomerService { //etc.

これで、ファイルの先頭にあるAngularから2つのものをインポートする準備ができました。 最初は Injectable() 前に述べた注釈:

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

次に、HttpClientが必要です。

import { HttpClient } from '@angular/common/http';

これで、これをAngularサービスにする準備が整いました。

サービスクラスをAngularに更新する

まず、を追加しましょう Injectable() クラスのすぐ上にあるCustomerServiceへのアノテーション:

@Injectable()

このアノテーションに渡されるオプションオブジェクトはありません。

次に行う必要があるのは、AngularJSへのすべての参照を置き換えることです $http AngularのHttpClientを使用したサービス。 速記を使用します http 代わりに、このドキュメントで検索と置換を実行し、変更します $httphttp、ほとんどの呼び出しがほぼ同じであることを考えると、次のようになります。

次に、httpプロパティの作成方法について1つ変更する必要があります。 これの代わりに:


//customer.service.ts

class CustomerService{

    http: any;

    constructor(http) {

        this.http = http;

    }

…のパブリックプロパティを宣言する6行目を削除します http タイプの any. 代わりに、コンストラクターに、 private 前のキーワード http タイプであることを指定します HttpClient:


//customer.service.ts

export class CustomerService{

    constructor(private http: HttpClient) {  }

Angularの依存性注入により、CustomerServiceでHttpClientサービスのプライベートインスタンスをインスタンス化しています。 private キーワード、クラスインスタンスを設定する必要はありません http 注入されたインスタンスと同じです(これは舞台裏で行われます)。

私たちが今持っているのはAngularサービスの骨組みですが、私たちが使用するすべての場所の下に赤い波線が表示されます .then. IntelliSenseが、応答の監視可能なタイプにプロパティが存在しないことを通知していることがわかります。

そこで何が起こっているのですか? 次にそれに取り組みましょう。

オブザーバブルの約束への変換

カスタマーサービスをAngularサービスに大幅に書き直しましたが、使用しようとすると少し問題があります .then これらのhttp呼び出しで。 これは、AngularのHttpClientがpromiseではなくobservableを返すためです。 ここには2つの選択肢があります。

  1. 実用的な方法:これらの応答をpromiseに変換すると、アプリケーションの残りの部分は同じように機能します。

  2. 楽しい方法:これらの応答を監視可能なものとして保持し、コンポーネントを更新します。

大規模なリファクタリングやアップグレードの場合、目標は常にアプリケーションの稼働時間をできるだけ少なくすることです。 推奨されるアプローチは、最初に呼び出しをpromiseに変換することです。 このようにして、アプリケーションのどのコンポーネントやその他の部分がサービスとその呼び出しに依存しているかを判断できます。 それが完了したら、呼び出しを一度に1つずつオブザーバブルに変換し、それに応じて各コンポーネントを更新できます。 したがって、最初に、Angularにサービスを渡して、それを機能させます。 次に、適切なタイミングであると感じたときに、オブザーバブルを使用することを心配します。

それでは、最初に呼び出しをpromiseに変換しましょう。 ただし、心配しないでください。少しの間、楽しいことをして、呼び出しを監視可能なものに変換します。

toPromise演算子の使用

オブザーバブルをpromiseに変換するには、まず、オブザーバブルを処理するライブラリであるRxJSからインポートする必要があります。 Angularのインポート後、次を追加する必要があります。

import { Observable } from 'rxjs/Observable';

これにより、RxJSが提供する監視可能なオブジェクトにさまざまな関数を使用できます。

The toPromise メソッドを使用すると、オブザーバブルをプロミスに変換できます。 以前のバージョンのRxJSでは個別にインポートされていましたが、現在は Observable. 個々の演算子をインポートすることはRxJSの一般的なパターンですが、必要な演算子とそれらがライブラリ内のどこにあるかを理解するのは少し難しい場合があります。 RxJSが提供するドキュメントリソース、およびRxJSに関するAngularドキュメントを必ず確認してください。

これで、 toPromise それぞれの前の演算子 .then 私たちの呼び出しで。 これを行うと、次のようなエラーも表示されます .data タイプ「オブジェクト」に存在するプロパティではありません。 これは、応答がHTTP応答内のデータオブジェクトをすでに返しているためです。 次に行う必要があるのは、 .data. これは元の時代とは異なります Http サービス、私たちが呼び出す必要があった場所 .json データを返す関数。

もう一つ。 TypeScriptの利点があるので、これらの各関数に戻り型を追加しましょう。 TypeScriptでは、技術的には必須ではありませんが、可能な場合は型を指定するのが常に最善です。 したがって、各関数名の後に追加します :Promise<any>.

サービスで完成した関数は次のようになります。


//customer.service.ts

getCustomers():Promise<any> {

   return this.http.get('/api/customers')

       .toPromise()

        .then(response => response);

}


getCustomer(id):Promise<any> {

    return this.http.get(`/api/customers/${id}`)

        .toPromise()

        .then(response => response);

}


postCustomer(customer):Promise<any> {

    return this.http.post('/api/customers', customer)

       .toPromise()

       .then(data => data);

}

これにより、promiseへの呼び出しでオブザーバブルを正常に変換できました。

カスタマーサービスのダウングレード

オブザーバブルをpromiseに変換したので、まだ移行されていないAngularJSコンポーネントで引き続き使用できるように、カスタマーサービスをダウングレードする準備が整いました。

このプロセスは、前のガイドでホームコンポーネントをダウングレードしたときと似ています。 最初に行う必要があるのは、インポートすることです downgradeInjectable インポートしたのと同じように、ngUpgradeライブラリの関数 downgradeComponent ホームコンポーネント用。 したがって、2行目の後に、次を追加します。

import { downgradeInjectable } from '@angular/upgrade/static';

また、という変数を宣言する必要があります angular ホームコンポーネントで行ったように。 したがって、4行目の後に、次を追加します。

declare var angular: angular.IAngularStatic;

次に、ファイルの最後に、サービスをダウングレードされたファクトリとして登録します。 したがって、クラスの終了後、次のように入力します。


angular.module('app')

    .factory('customerService', downgradeInjectable(CustomerService));

CustomerServiceをAngularJSで利用できるようにダウングレードしました。 完成したサービスは次のとおりです。


//customer.service.ts

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

import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';

import { downgradeInjectable } from '@angular/upgrade/static';

declare var angular: angular.IAngularStatic;


@Injectable()

export class CustomerService {

    constructor(private http: HttpClient) {}


    getCustomers():Promise<any> {

        return this.http.get('/api/customers')

            .toPromise()

            .then(response => response);

    }


    getCustomer(id):Promise<any> {

        return this.http.get(`/api/customers/${id}`)

            .toPromise()

            .then(response => response);

    }


    postCustomer(customer):Promise<any> {

        return this.http.post('/api/customers', customer)

            .toPromise()

            .then((data) => data);

    }

}


angular.module('app')

    .factory('customerService', downgradeInjectable(CustomerService));

サービスをAngularモジュールに移動する

カスタマーサービスはAngularサービスに書き直され、AngularJSで利用できるようにダウングレードされました。 次に、AngularJSモジュールの参照を削除し、Angularモジュールに追加する必要があります。

AngularJSモジュールからのコンテンツの削除

まず、AngularJSモジュールを開きましょう(app.module.ajs.ts). 22行目を削除できます。

import CustomerService from './customers/customerService';

…および41行目:

.service('customerService', CustomerService)

これらは、このモジュールで行う必要のあるすべての変更です。

サービスをAngularモジュールに移動する

それでは、私たちのサービスを私たちに追加しましょう NgModuleapp.module.ts Angularコードがアクセスできるようにします。 最初に行う必要があるのは、7行目以降のサービスをインポートすることです。

import { CustomerService } from './customers/customer.service';

アプリケーションにカスタマーサービスを登録するには、次の配列を追加する必要があります。 providers 私たちに NgModule 私たちの後 entryComponents そこにCustomerServiceを配列して追加します。


//app.module.ts

providers: [

        CustomerService

    ]

The providers 配列は、アプリケーションにすべてのサービスを登録する場所です。 これで、カスタマーサービスがNgModuleに登録され、準備が整いました。

AOTコンパイルに関するクイックノート

ダウングレードのこの方法(ダウングレードされたサービスをサービスファイルに登録し、AngularJSモジュールファイルから削除する)は、開発や、デプロイする前にアプリケーションをすばやく書き直す予定の場合に最適です。 ただし、本番用のAngularAOTコンパイラはこのメソッドでは機能しません。 代わりに、AngularJSモジュールにダウングレードされたすべての登録が必要です。

ダウングレードは同じですが、代わりに次のようにします。

  • 輸入 downgradeInjectableapp.module.ajs.ts (あなたはすでに持っています angular そこにあるので、宣言する必要はありません)。

  • のインポートを変更します CustomerServiceimport { CustomerService } from './customers/customer.service'; 名前付きエクスポートに切り替えたので。

  • サービス登録を上記とまったく同じ工場登録に変更します。

アプリケーションの機能のテスト

アプリケーションがまだ実行されていることを確認することをお勧めします。 Express APIを起動してから、Webpack開発サーバーを実行してみましょう。 ターミナルを開き、次のコマンドを実行してExpressを起動します。


cd server

npm start

次に、別のターミナルを開き、次のコマンドを実行してWebpackを起動します。


cd public

npm run dev

すべてが正しくコンパイルおよびバンドルされていることを確認する必要があります。

次に、ブラウザを開いて、 localhost:9000. お客様のルートに移動して、サービスが機能しているかどうかを確認しましょう。

Chromeデベロッパーツールの[ソース]タブに移動し、customersフォルダーに移動して、CustomerServiceソースをクリックすることで、書き換えられたAngularサービスを使用していることを再確認できます。

これは私たちの書き直されたサービスを示しています。 サービスをAngularに更新しましたが、customersコンポーネントとcustomer tableコンポーネントの両方で使用されており、どちらもAngularJSに残っています。

GetCustomersをオブザーバブルとして使用する

CustomerServiceがダウングレードされて機能するようになったので、楽しんでそれを使用しましょう getCustomers オブザーバブルとして呼び出します。 そうすれば、オブザーバブルのすべての新機能を利用できるようになります。 顧客コンポーネントと注文コンポーネントの両方で呼び出しを使用しているため、これは少し注意が必要です。どちらもまだAngularに書き換えられていません。 心配しないでください-これを行う方法を段階的に説明します。

カスタマーサービスコードに戻ると、最初に行う必要があるのは、16行目の返品タイプを次のように変更することです。 Observable<any>. もちろん今、TypeScriptはPromiseに変換しているので文句を言っているので、両方を削除する必要があります toPromisethen 機能。 今は次のようになっています。


getCustomers():Observable<any> {

      return this.http.get('/api/customers');

}

次に、promiseの代わりにobservableを使用するようにcustomersコンポーネントを更新する必要があります。 次にそれを行います。

CustomersコンポーネントでのObservableの使用

私たちの getCustomers 呼び出しは現在、observableに戻っています。 顧客コンポーネントを更新しましょう(customers.ts)promiseの代わりにobservableを使用する。 CustomersコンポーネントはまだAngularJSコンポーネントであり、それで問題ありません。まだそれをいじる必要はありませんが、少しTypeScriptを使用して支援しましょう。 ファイルの先頭にあるCustomerServiceをインポートしましょう。

import { CustomerService } from './customer.service';

CustomerServiceをインポートしたので、コントローラー関数定義で挿入されたCustomerServiceのタイプを指定できます。


//customers.ts

function customersComponentController(customerService: CustomerService){

TypeScriptが私たちについて不平を言うという利点があります .then CustomerServiceの場合と同じように。 それはそれを知っています getCustomers 呼び出しは、オブザーバブルとそれを返すことになっています .then オブザーバブルには存在しません。

AngularJSコンポーネントであろうとAngularコンポーネントであろうと、コンポーネントでobservableを使用する方法は、サブスクライブすることです。 このオブザーバブルをサブスクライブする前に、インポートする必要があります Observable 私たちが奉仕でしたように。 したがって、CustomerServiceインポートの上に、次を追加します。

import { Observable } from 'rxjs/observable';

これにより、次のようなobservableの関数を使用できるようになります。 subscribe. だから、今私たちの内側の18行目 $onInit 関数、私たちはただ変更することができます thensubscribe、そして他のすべては同じままでいられます。

ブラウザを見て、これが期待どおりに機能するかどうかを確認してみましょう。 顧客ルートに向かうと、すべてが同じように機能していることがわかります。 ただし、[注文]タブに移動すると、大きな問題が発生します。データがなく、 TypeError: Cannot read property 'fullName' of undefined コンソールで。 何が起きてる?

注文コンポーネントが使用していることがわかります getCustomers 電話しますが、それでも約束として使用しようとしています。 それを修正しましょう。

Ordersコンポーネントの修正

getCustomersの呼び出しを、promiseではなく監視可能なものに書き直したときに、誤って注文コンポーネントを壊してしまいました(orders/orders.ts)、これはまだAngularJSにあります。 それは私たちの $onInit 関数、私たちは使用しています $q.all ビューモデルにデータを割り当てる前に、2つのプロミスが返されるのを待ちます。


vm.$onInit = function() {

        let promises = [orderService.getOrders(), customerService.getCustomers()];

        return $q.all(promises).then((data) => {

            vm.orders = data[0];

            vm.customers = data[1];

            vm.orders.forEach(function (order) {

                var customer = _.find(vm.customers, function (customer) {

                    return order.customerId === customer.id;

                });

                order.customerName = customer.fullName;

            });

        });

    };

これはAngularJSの一般的なパターンでした。

この問題の1つの解決策は、注文コンポーネントをAngularに書き直し、注文サービスも書き直すことです。 しかし、現実の世界では、それがすぐに可能であるとは限りません。 大規模なリファクタリングでは、ダウンタイムを最小限に抑え、常に本番環境にデプロイできる継続的に提供可能なアプリケーションを用意できるようにすることが最優先事項であることを忘れないでください。

ただし、注文コンポーネントがはるかに複雑で、書き直す時間がなかった場合はどうなりますか? その場合、2つの選択肢があります。 getCustomers 注文コンポーネントでpromiseを呼び出すか、変換することができます getOrders 観察可能なものに約束します。

変換する getCustomers コンポーネントで約束するために、サービスで以前に行ったのとまったく同じことを実行します-インポート Observable RxJSから追加し、 toPromise 後の演算子 getCustomers. これはとても簡単で、まだオブザーバブルを使用するためにこのコンポーネントをリファクタリングする時間がない場合に便利なトリックです。 ただし、私たちの長期的な目標は、約束を完全に取り除き、完全にオブザーバブルに切り替えることであるため、完全に望ましいわけではありません。 だから、Ietは私たちを変換します getOrders ここでオブザーバブルを呼び出します。

変換 getCustomers 約束へ

変換してみましょう getOrders 観測量に。 最初に行うことは、customerコンポーネントで行ったのと同じように、ファイルの先頭にCustomerServiceをインポートすることです。

import { CustomerService } from '../customers/customer.service';

次に、コントローラー関数定義で、挿入されたCustomerServiceのタイプを指定できます。


//orders.ts

function ordersComponentController(orderService, customerService: CustomerService, $q) {

変換するために getOrders observableを呼び出します。observableで2つの静的メソッドを使用します。 fromPromiseforkJoin. The fromPromise メソッドを使用すると、promiseをobservableに変換できます。 forkJoin 複数のオブザーバブルをサブスクライブできます。 したがって、最初に行う必要があるのは、ファイルの先頭にあるこれら2つのメソッドをインポートすることです。


import { fromPromise } from 'rxjs/observable/fromPromise';

import { forkJoin } from 'rxjs/observable/forkJoin';.

今、私たちは私たちの中でいくつかの仕事をすることができます $onInit 関数。 21行目より上で、ordersDataという変数を宣言し、fromPromiseメソッドを使用してみましょう。

let ordersData = fromPromise(orderService.getOrders());

さあ、書き直しましょう $q.all 使用する forkJoin 代わりは。 したがって、最初に置き換えるだけです return $q.allforkJoin. 配列を渡す必要があるので、移動しましょう promises 配列して追加 ordersData その前にそしてそれからちょうど取り除く promises 宣言。 最後に、変更しましょう .then.subscribe 単一のオブザーバブルと同じように。 これが完成です $onInit 関数:


vm.$onInit = function() {

        let ordersData = fromPromise(orderService.getOrders());

        forkJoin([ordersData, customerService.getCustomers()]).subscribe((data) => {

            vm.orders = data[0];

            vm.customers = data[1];

            vm.orders.forEach(function (order) {

                var customer = _.find(vm.customers, function (customer) {

                    return order.customerId === customer.id;

                });

                order.customerName = customer.fullName;

            });

        });

    };

ここで行ったことを要約してみましょう。 まず、 fromPromise そして私たちを変換しました getOrders 約束から観察可能なものへの呼び出し。 次に、 forkJoin 両方を購読するには ordersData そしてその getCustomers 電話。 と同じように $q.all、サブスクライブ forkJoin リストした順序でデータの配列を返します。 そう、 data[0] 私たちの注文になります、そして data[1] お客様になります。

これをクリーンアップするためにもう1つ実行しましょう。 削除できます $q 16行目からの依存関係 $inject 関数定義の配列と167行目。

アプリケーションが機能していることを確認する

もう一度ブラウザを見て、これが機能することを確認しましょう。 アプリケーションが正しくコンパイルおよびロードされることを確認する必要があるため、[注文]タブを確認してください。

これは、データが正しく読み込まれていることを示しています。 これで、promiseとobservableの間を行き来する方法を見てきました。これは、アップグレード中にすべてを一度にobservableに変換できない大規模なアプリケーションで作業している場合に便利です。

結論

ここから、このガイドと最後のガイドを使用して、 customersTable コンポーネントと products ルート。 Angularのテンプレート構文を使用していくつかの新しいトリックを学ぶ必要がありますが、それ以外の場合は、必要なものがすべて揃っています。