Angular 2をオンにする:1.5からのアップグレード

公開: 2022-03-11

私は、アプリをAngular1.5からAngular2にアップグレードするためのステップバイステップのガイドを書きたいと思い始めました。その後、編集者から小説ではなく記事が必要であると丁寧に通知されました。 多くの検討の結果、Angular 2の変更についての広範な調査から始める必要があることを受け入れました。これは、JasonAdenのGettingPast Hello World inAngular2の記事で取り上げられているすべてのポイントに当てはまります。 …おっと。 先に進んでそれを読んでAngular2の新機能の概要を理解してください。ただし、実践的なアプローチのために、ブラウザーをここに置いてください。

これを、最終的にデモアプリをAngular 2にアップグレードするプロセス全体を網羅するシリーズにしたいと思います。ただし、今のところ、単一のサービスから始めましょう。 コードを曲がりくねって歩きましょう。次のような質問に答えます。

「ああ、なぜすべてがそんなに違うのか」

Angular:古い方法

あなたが私のようなら、Angular2クイックスタートガイドはTypeScriptを初めて見たかもしれません。 非常に迅速に、独自のWebサイトによると、TypeScriptは「プレーンJavaScriptにコンパイルされるJavaScriptの型付きスーパーセット」です。 トランスパイラー(BabelやTraceurと同様)をインストールすると、ES2015とES2016の言語機能と強い型付けをサポートする魔法の言語になります。

この難解な設定が厳密に必要なものではないことを知って安心できるかもしれません。 昔ながらのJavaScriptでAngular2コードを書くことはそれほど難しいことではありませんが、そうする価値はないと思います。 おなじみの領域を認識するのは良いことですが、Angular 2の新しくてエキサイティングなことの多くは、新しいアーキテクチャではなく、新しい考え方です。

この投稿では、サービスを1.5からAngular2にアップグレードする方法について説明します。

Angular 2の新しくてエキサイティングなのは、新しいアーキテクチャではなく、新しい考え方です。
つぶやき

それでは、Angular1.5から2.0.0-beta.17にアップグレードしたこのサービスを見てみましょう。 これはかなり標準的なAngular1.xサービスであり、コメントで注目しようとした興味深い機能がいくつかあります。 標準のおもちゃのアプリケーションよりも少し複雑ですが、実際に行っているのは、Airbnbなどのレンタルプロバイダーからのリストを集約する無料で利用可能なAPIであるZilyoにクエリを実行することだけです。 申し訳ありませんが、かなりのコードです。

zilyo.service.js(1.5.5)

 'use strict'; function zilyoService($http, $filter, $q) { // it's a singleton, so set up some instance and static variables in the same place var baseUrl = "https://zilyo.p.mashape.com/search"; var countUrl = "https://zilyo.p.mashape.com/count"; var state = { callbacks: {}, params: {} }; // interesting function - send the parameters to the server and ask // how many pages of results there will be, then process them in handleCount function get(params, callbacks) { // set up the state object if (params) { state.params = params; } if (callbacks) { state.callbacks = callbacks; } // get a count of the number of pages of search results return $http.get(countUrl + "?" + parameterize(state.params)) .then(extractData, handleError) .then(handleCount); } // make the factory return { get : get }; // boring function - takes an object of URL query params and stringifies them function parameterize(params) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } // interesting function - takes the results of the "count" AJAX call and // spins off a call for each results page - notice the unpleasant imperativeness function handleCount(response) { var pages = response.data.result.totalPages; if (typeof state.callbacks.onCountResults === "function") { state.callbacks.onCountResults(response.data); } // request each page var requests = _.times(pages, function (i) { var params = Object.assign({}, { page : i + 1 }, state.params); return fetch(baseUrl, params); }); // and wrap all requests in a promise return $q.all(requests).then(function (response) { if (typeof state.callbacks.onCompleted === "function") { state.callbacks.onCompleted(response); } return response; }); } // interesting function - fetch an individual page of results // notice how a special callback is required because the $q.all wrapper // will only return once ALL pages have been fetched function fetch(url, params) { return $http.get(url + "?" + parameterize(params)).then(function(response) { if (typeof state.callbacks.onFetchPage == "function") { // emit each page as it arrives state.callbacks.onFetchPage(response.data); } return response.data; // took me 15 minutes to realize I needed this }, (response) => console.log(response)); } // boring function - takes the result object and makes sure it's defined function extractData(res) { return res || { }; } // boring function - log errors, provide teaser for greater ambitions function handleError (error) { // In a real world app, we might send the error to remote logging infrastructure var errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return errMsg; } } // register the service angular.module('angularZilyoApp').factory('zilyoService', zilyoService);

この特定のアプリのしわは、地図上に結果を表示することです。 他のサービスは、ページ付けまたはレイジースクローラーを実装することで結果の複数のページを処理します。これにより、一度に1ページの結果を取得できます。 ただし、検索領域内にすべての結果を表示したいので、すべてのページが読み込まれたときに突然表示されるのではなく、サーバーから戻ったらすぐに表示されるようにします。 さらに、進行状況の更新をユーザーに表示して、ユーザーが何が起こっているのかをある程度把握できるようにします。

関連: AngularJSインタビューの重要なガイド

Angular 1.5でこれを実現するために、コールバックを使用します。 onCompletedコールバックをトリガーする$q.allラッパーからわかるように、Promiseは途中まで到達しますが、それでもかなり厄介になります。

次に、lodashを取り込み、すべてのページリクエストを作成します。各リクエストは、 onFetchPageコールバックを実行して、利用可能になり次第マップに追加されるようにします。 しかし、それは複雑になります。 コメントからわかるように、私は自分の論理に迷い、何がいつどの約束に戻されているのかを把握できませんでした。

コードの全体的なすっきりは、(厳密に必要なものよりはるかに)さらに悪化します。混乱すると、そこから下向きにスパイラルするだけだからです。 私と一緒に言ってください…

「これはより良い方法である必要があります」

Angular 2:新しい考え方

もっと良い方法があります、そして私はあなたにそれを示すつもりです。 ES6(別名ES2015)の概念にあまり時間をかけるつもりはありません。なぜなら、そのことについて学ぶのにはるかに良い場所があるからです。出発点が必要な場合は、ES6-Features.orgに概要があります。すべての楽しい新機能の。 この更新されたAngularJS2コードを検討してください。

zilyo.service.ts(2.0.0-beta.17)

 import {Injectable} from 'angular2/core'; import {Http, Response, Headers, RequestOptions} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; @Injectable() export class ZilyoService { constructor(private http: Http) {} private _searchUrl = "https://zilyo.p.mashape.com/search"; private _countUrl = "https://zilyo.p.mashape.com/count"; private parameterize(params: {}) { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); } get(params: {}, onCountResults) { return this.http.get(this._countUrl, { search: this.parameterize(params) }) .map(this.extractData) .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; }) .flatMap(results => Observable.range(1, results.totalPages)) .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); }) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response) { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body = res.json(); return body.result || { }; } private handleError (error: any) { // In a real world app, we might send the error to remote logging infrastructure let errMsg = error.message || 'Server error'; console.error(errMsg); // log to console instead return Observable.throw(errMsg); } }

いいね! この行を1行ずつ見ていきましょう。 繰り返しになりますが、TypeScriptトランスパイラーでは、すべてをバニラJavaScriptに変換するため、必要なES6機能を使用できます。

最初のimportステートメントは、ES6を使用して必要なモジュールをロードするだけです。 私はES5(別名通常のJavaScript)で開発のほとんどを行っているので、使用する予定のすべてのオブジェクトの一覧表示を突然開始する必要があるのは少し面倒であることを認めなければなりません。

ただし、TypeScriptはすべてをJavaScriptにトランスパイルし、SystemJSを密かに使用してモジュールのロードを処理していることに注意してください。 依存関係はすべて非同期で読み込まれ、インポートしていないシンボルを取り除く方法でコードをバンドルすることができます(伝えられるところでは)。 さらに、それはすべて「積極的な縮小」をサポートしますが、これは非常に苦痛に聞こえます。 これらの輸入明細書は、そのすべてのノイズに対処することを避けるために支払うべき小さな代償です。

Angularのインポートステートメントは、舞台裏で多くのことを行います。

輸入明細書は、舞台裏で起こっていることに対して支払うための小さな代償です。

とにかく、Angular 2自体から選択的な機能をロードすることは別としてimport {Observable} from 'rxjs/Observable'; 。 RxJSは、Angular 2の基盤となるインフラストラクチャの一部を提供する、驚くほどクールなリアクティブプログラミングライブラリです。

ここで、 @Injectable()に移動します。

それが正直に何をするのかはまだ完全にはわかりませんが、宣言型プログラミングの利点は、必ずしも詳細を理解する必要がないことです。 これはデコレータと呼ばれ、それに続くクラス(または他のオブジェクト)にプロパティを適用できる豪華なTypeScriptコンストラクトです。 この場合、 @Injectable()は、コンポーネントに注入する方法をサービスに指示します。 最高のデモンストレーションは馬の口から直接行われますが、かなり長いので、AppComponentでどのように表示されるかを簡単に確認します。

 @Component({ ... providers: [HTTP_PROVIDERS, ..., ZilyoService] })

次はクラス定義そのものです。 その前にexportステートメントがあります。つまり、ご想像のとおり、サービスを別のファイルにimportできます。 実際には、上記のように、サービスをAppComponentコンポーネントにインポートします。

@Injectable()は、コンポーネントに注入する方法をサービスに教えます。

@Injectable()は、コンポーネントに注入する方法をサービスに教えます。

その直後にコンストラクターがあり、実際の依存性注入が実際に行われているのを見ることができます。 行constructor(private http:Http) {}は、TypeScriptが魔法のようにHttpサービスのインスタンスとして認識するhttpという名前のプライベートインスタンス変数を追加します。 ポイントはTypeScriptに行きます!

その後、実際の肉やジャガイモに到達する前の、通常のインスタンス変数とユーティリティ関数であるget関数です。 ここでは、 Httpが動作しているのがわかります。 Angular 1のプロミスベースのアプローチによく似ていますが、内部的にはかなりクールです。 RxJSに基づいて構築されているということは、Promiseよりも2つの大きな利点があることを意味します。

  • 応答が気にならなくなった場合は、 Observableをキャンセルできます。 これは、先行入力オートコンプリートフィールドを作成していて、「cat」に入力した後の「ca」の結果を気にしない場合に当てはまる可能性があります。
  • Observableは複数の値を発行でき、サブスクライバーはそれらが生成されるときにそれらを消費するために何度も呼び出されます。

最初のものは多くの状況で素晴らしいですが、それは私たちが新しいサービスで焦点を合わせている2番目です。 get関数を1行ずつ見ていきましょう。

 return this.http.get(this._countUrl, { search: this.parameterize(params) })

これは、Angular 1で見られるpromiseベースのHTTP呼び出しと非常によく似ています。この場合、一致するすべての結果のカウントを取得するためにクエリパラメーターを送信しています。

 .map(this.extractData)

AJAX呼び出しが戻ると、応答をストリームに送信します。 メソッドmapは、概念的には配列のmap関数に似ていますが、同期性または非同期性に関係なく、アップストリームで発生した処理が完了するのを待機するため、promiseのthenメソッドのように動作します。 この場合、応答オブジェクトを受け入れ、JSONデータをティーズアウトしてダウンストリームに渡します。 今、私たちは持っています:

 .map(results => { if (typeof onCountResults === "function") { onCountResults(results.totalResults); } return results; })

そこにスライドする必要がある厄介なコールバックがまだ1つあります。 ほら、それはすべて魔法ではありませんが、AJAX呼び出しが戻るとすぐに、すべてストリームを離れることなくonCountResultsを処理できます。 それは悪くないです。 次の行について:

.flatMap(results => Observable.range(1、results.totalPages))

ええと、あなたはそれを感じることができますか? 見ている群衆に微妙な静けさがやって来て、何か大きなことが起きようとしていることがわかります。 この線はどういう意味ですか? 右側の部分はそれほどクレイジーではありません。 これはRxJS範囲を作成します。これは、栄光に満ちたObservableでラップされた配列だと思います。 results.totalPagesが5に等しい場合、 Observable.of([1,2,3,4,5])のような結果になります。

flatMapは、それを待つ、 flattenmapの組み合わせです。 Egghead.ioには概念を説明するすばらしいビデオがありますが、私の戦略は、すべてのObservableを配列として考えることです。 Observable.rangeは独自のラッパーを作成し、2次元配列[[1,2,3,4,5]]を残します。 flatMapは、外側の配列をフラット化し、 [1,2,3,4,5]を残します。次に、 mapは配列上に単純にマップし、値を一度に1つずつ下流に渡します。 したがって、この行は整数( totalPages )を受け入れ、それを1からtotalPagesまでの整数のストリームに変換します。 それほど多くはないように思われるかもしれませんが、設定する必要があるのはそれだけです。

プレステージ

その影響力を高めるために、これを1行にまとめたかったのですが、すべてを勝ち取ることはできないと思います。 ここでは、最後の行に設定した整数のストリームがどうなるかを確認します。 それらはこのステップに1つずつ流れ込み、ページパラメータとしてクエリに追加されてから、最終的に新しいAJAXリクエストにパッケージ化され、結果のページをフェッチするために送信されます。 そのコードは次のとおりです。

 .flatMap(i => { return this.http.get(this._searchUrl, { search: this.parameterize(Object.assign({}, params, { page: i })) }); })

totalPagesが5の場合、5つのGETリクエストを作成し、それらをすべて同時に送信します。 flatMapは新しいObservableごとにサブスクライブするため、リクエストが(任意の順序で)返されると、ラップが解除され、各応答(結果のページなど)が一度に1つずつダウンストリームにプッシュされます。

この全体がどのように機能するかを別の角度から見てみましょう。 元の「カウント」リクエストから、結果の合計ページ数を見つけます。 ページごとに新しいAJAXリクエストを作成し、ページがいつ戻るか(またはどのような順序で)に関係なく、準備が整うとすぐにストリームにプッシュされます。 コンポーネントが行う必要があるのは、 getメソッドによって返されるObservableをサブスクライブすることだけです。これにより、各ページが1つのストリームから次々に受信されます。 それを取る、約束します。

各応答は、一度に1つずつダウンストリームにプッシュされます。

コンポーネントは、すべて単一のストリームから各ページを次々に受信します。

その後は、すべて少し反気候的です。

 .map(this.extractData).catch(this.handleError);

各応答オブジェクトがflatMapから到着すると、そのJSONはカウント要求からの応答と同じ方法で抽出されます。 最後に、 catch演算子があります。これは、ストリームベースのRxJSエラー処理がどのように機能するかを説明するのに役立ちます。 これは、 Observableオブジェクトが非同期エラー処理でも機能することを除いて、従来のtry/catchパラダイムと非常によく似ています。

エラーが発生すると、ダウンストリームで競合し、エラーハンドラーが発生するまで過去の演算子をスキップします。 この場合、 handleErrorメソッドはエラーを再スローし、サービス内でエラーをインターセプトできるようにするだけでなく、サブスクライバーがさらにダウンストリームで起動する独自のonErrorコールバックを提供できるようにします。 エラー処理は、すでに達成したすべての優れた機能を使用しても、ストリームを十分に活用していないことを示しています。 HTTPリクエストの後にretry演算子を追加するのは簡単です。これにより、エラーが返された場合に個々のリクエストが再試行されます。 予防策として、 rangeジェネレーターとリクエストの間に演算子を追加し、一度に大量のリクエストでサーバーにスパムを送信しないように、何らかの形式のレート制限を追加することもできます。

関連:フリーランスのAngularJS開発者の上位3%を雇います。

要約:Angular 2の学習は、新しいフレームワークだけではありません

Angular 2を学ぶことは、まったく新しい家族に会うようなものであり、彼らの関係のいくつかは複雑です。 うまくいけば、これらの関係が何らかの理由で進化したことを示すことができました。このエコシステム内に存在するダイナミクスを尊重することで得られるものはたくさんあります。 私は表面をほとんど傷つけていないので、この記事も楽しんでいただければ幸いです。このテーマについては、まだまだ多くのことが言えます。

関連:すべての特典、面倒なし:Angular9チュートリアル