AngularJS:1.2から1.4への移行、パート1

バージョン1.2からアップグレードする利点については、多くの記事が書かれています。 ただし、統計によると、45%以上のサイトが引き続きバージョン1.2を使用しており、新しい1.3に切り替えたのは31%のみで、1.4を使用しているのは5%のみです。

そして、これは宇宙船が宇宙の広がりを耕すときです。バージョン1.2.0はほぼ2年前にリリースされ、バージョン1.3.0-1年前、バージョン1.4.0-この春、そして1.5.0はすでにベータ版です。



原則として、移行からの大規模なプロジェクトは、このプロセスの不透明性とこのトピックに関する資料の不足を妨げます。
公式ガイドでは、発生する可能性のあるすべての問題のほんの一部を見つけることができます。また、ブログでは、原則として、それを再確認するだけです。

この記事では、新しいバージョンへの移行時に発生する可能性のある問題について説明し、最も問題のある場所を分析します。

残念ながら、投稿が大きすぎたため、このパートではこの移行の重大な変更に焦点を当て、次のパートで機能と利点について説明します。 しかし、移行の主な利点は、作業速度の大幅な向上と修正されたバグのリストです(結局、1.2の最新の修正は1年前の1.2.28でした)。

この投稿を2つの部分に分けました。 そのうちの1つは、2つのプロジェクトを新しいバージョンに変換した個人的な経験です(ネタバレの下に隠しました)。2つ目は、発見された問題のリストです。

移行のストーリー
1.2から1.3への移行を必要とする最初の比較的大きなプロジェクトは、12,000行を超えるコード(主にディレクティブ)にもかかわらず、問題が発生しないEラーニングプラットフォームでした。

2番目のプロジェクトでは、バージョン1.2.16から1.4.4への移行が必要で、かなり大きく(純粋な角度の約75,000行)、製品(アカウンティング)の詳細が複雑な関係、多くの形態、避けられない問題を暗示していましたが、以前の経験は勇気づけられました。

移行の最初の結果は、コンソールに多数のエラーを含む破損したアプリケーションを取得することが予想されていましたが、アプリケーションは起動し、問題はありませんでした。 それは非常に奇妙だったので、私は公式ガイドを通して旅を始めましたが、説明された問題はどれも再現されませんでした。 ジョブが完了したと判断したので、QAと自動テストにタスクを割り当てました。

そして翌日、私は9個のバグを受け取り、さらに9個のバグを受け取りました。 見つかったすべてのエラーのcさは、アプリケーションを壊すことではなく、動作を感知できないほど変更することです。

ここで、 変更ログとそこに見つかったコミット内の議論は、理由を探して友と仲間になりました。

最初のステップとして、サーバー側の非同期チェックが失敗しました。これはステータスではなく、「OK」などのテキストプリミティブの形式の回答で機能しました。 これについて通知されるブレークの変更は1つではありませんが、対応するバグ修正がありました。

ヒント: XHRリクエストで作業を確認してください。 サーバーがオブジェクトを送信せず、ヘッダー「application / json」を持つプリミティブがある場合、問題が発生します。

次に、ポップアップにネストされたスピナーが落ち、親の幅を正しく決定するために停止しました。 問題は2つありました。 まず、スピナーはng-showにありました 。これは、親コンテナが表示される前に初期化されたことを意味します。 第二に、スピナーは$ observeを介して属性に続いて/ hid を表示しました 。 何らかの理由で、古いバージョンでは、親が:visibleになってから属性が変更され、新しい属性ではその逆になります。

ヒント:
  1. ng-show / ng-hideで初期化する際に重要な動的ディレクティブを保存しないでください。これにはng-ifを使用してください。 このヒントはバージョン1.2でも有効です。
  2. 他のデータ(DOMまたは$スコープ)への外部の変更を監視する場合は、 $ observeを使用して属性を追跡しないでください。 これには$ watch(function(){}、function(){})を使用します。 さらに、どちらのオプションもウォーターマークをダーティチェックに追加しますが、唯一の違いはコールバック呼び出し条件です。


この問題は、 $ eval内の式の条件にも影響しました。いくつかのブロックが消え始めました。 しかし、問題は別のバグ修正であることが判明し、すでにハブでカバーされいました

ヒント:フレームワークの文書化されていない機能を使用しないでください。 $スコープの次の値をチェックするときに使用するコードを調べてください: 'f''0''false''no''n''[]'

発生した主な問題は検証です。 バージョン1.3から、フォームをチェックするための新しい方法が角度から登場し、新しい落とし穴がありました。 ただし、新しい検証を使用する予定がない場合でも、問題が発生する可能性があります。 これらは、 maxlength / minlengthディレクティブと、フォームを永続的に無効にする恐れがある$ setValidityの新しいロジックです(これは私たちに起こったことです)。

新しいバージョンでは有効条件の1つがngModelCtrlの空のハッシュであるため、 ngModelCtrl

ヒント:公式のAPIで指定されていない$で始まるプロパティを入力しないでください。

ng-maxlength / ng-minlengthディレクティブと並行してマスク(電話、契約番号など)を使用する入力で別の問題が発生しました。

これらのディレクティブは両方とも、 ngModelCtrl。$ ModelValueの代わりにngModelCtrl。$ ViewValueをチェックします。これは、値 "xx-xx"の最大長が4ではなく5( "-"文字を考慮)であることを意味します。 アプリケーション全体で数百の検証ルールを書き直さなければならないので、すべてのng-maxlengthをカスタムmodel-maxlengthに置き換えて、モデルの再チェックのみを行うことにしました。 そして、その決定はひどいものでした! Angularはmaxlength制限属性を自分用に予約しました。つまり、入力する文字数を制限できなくなりました。 その結果、マスクシンボルを考慮して、すべてのルールを新しいルールに変更することが決定されました。 ただし、カスタムmodel-minlengthディレクティブは、事前定義された文字(たとえば、電話の "+7")があるディレクティブでアプリケーションを見つけたため、 ngModelCtrl。$ ViewValueで設定されたプレフィックスなしでモデルのみをチェックできます。

ヒント:マスクを使用する場合、またはngModelCtrl。$ ViewValueの値を操作する場合は、マスクの文字に基づいてチェックを変更します。 ng-minlengthチェックを使用して入力の事前定義値にプレースホルダー属性を使用するか、チェックをカスタムチェックに置き換えます。 このようなディレクティブの作業コードは、ブレーク変更リストにあります。

問題の2番目の波は、検証を新しいもの(さようなら、 $フォーマッター 、および$パーサー )に移行した後に発生しました。 多くのフォームが再び永久に無効になったという事実に直面しています。

同期検証( $ validators )を使用すると、問題がすぐに明らかになりました。 それは、フォームに検証チェック付きのng-showで隠されたフィールドがあるという事実にありました。 古いバージョン( $フォーマッター$パーサー作業の性質により、これらのフィールドはフォーム検証に含まれていませんでしたが、新しいバージョン( $バリデーター )では非表示の無効なフィールドが表示されます。

ヒント:非表示の動的フィールドを持つフォームがある場合、これらのフィールドを非表示にするng-ifで表示します。ng-show/ ng-hideは使用しないでください。

備考:非同期バリデーターの仕組み
非同期バリデーターはngModelCtrl。$ AsyncValidatorsコレクションに保存され、 Promiseを返す関数です。 Promiseはさまざまなサービス(たとえば、$ timeoutや$ http)を返し、特別なサービス$ qによっても生成されます。 フィールドの有効性は、返されたプロミスの解決または拒否に依存します。 非同期検証は、すべての同期検証が有効になった後にのみ開始されます。 バリデーターが呼び出されるとき、その妥当性( $ setValidity )は未定義になり、期待されるバリデーターの名前がngModelCtrl。$ Pendingに表示されます。 promiseの解決が発生するとすぐに、その有効性はnullに設定されます

検証関数を数回呼び出すと、最後の約束は前のものを消去します。 つまり、フィールドが2回検証され、最初のプロミスが解決され、2番目のプロミスが拒否された場合、フィールドは無効になります。

APIドキュメントの例:
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // Lookup user by username return $http.get('/api/users/' + value). then(function resolved() { //username exists, this means validation fails return $q.reject('exists'); }, function rejected() { //username does not exist, therefore this validation passes return true; }); }; 


非同期検証はより困難であることが判明しました。フォームは無効でしたが、エラーは含まれていませんでした。

ここでは、マスクの機能により( $ formattersおよび$ parsersを介して)、同期検証が完了する前に非同期検証が呼び出され、1つの変更された文字に対して数回呼び出されました。 これにより、複数の約束の作成バグが発生し、最後の約束が解決または拒否されなかったという事実に至りました。 したがって、入力は無期限に保留され、フォームはエラーなしで無効になりました。

そのような場合のバリデーターを構築する例
  var pendingPromise; ngModelCtrl.$asyncValidators.checkPhoneUnique = function (modelValue) { if (pendingPromise) { return pendingPromise; } var deferred = $q.defer(); if (modelValue) { pendingPromise = deferred.promise; $http.post('/', {value: modelValue}) .success(function (response) { if (response.Result === '   ') { deferred.resolve(); } else { deferred.reject(); } }).error(function () { deferred.reject(); }).finally(function () { pendingPromise = null; }); } else { deferred.resolve(); } return deferred.promise; }; 


ヒント:非同期バリデーターの動作をテストします。返されたプロミスが常に解決または拒否されることを確認します。

検証の最も奇妙な問題は、金額を入力するためのカスタムディレクティブでした(ルーブルとセントの2つの入力のように見え、ngModelにこれらの数値を連結した既製の結果を与えます)。 彼女は無効であるだけでなく、フレームワークの奥からコンソールにエラーを伝えました。

問題に費やされた時間はかなり良かった、問題は再びng-maxlengthであり、解決策は簡単だった:$ formatValuesディレクティブの文字列に$ viewValue値を追加しました。

しかし、なぜ問題が発生したのでしょうか? それを理解しましょう!

それはすべて$コンパイルの作業に関するものです。メインディレクティブの動作は内部に記述されています。 たとえば、 inputおよびtextareaの場合、 inputDirectiveコントロールが呼び出されます 。これはattr.typeを受け取り、それに基づいてinputTypeコレクションの関数の1つを呼び出します。 テキストタイプの場合、これはtextInputTypeです。 次に、彼はコントロールをstringBasedInputType関数にスローします。この関数は、値を$ formattersの文字列に変換するためのコードを追加します。

したがって、 ngModelが inputなどの既存の基本要素に関連付けられていないが、たとえば単純な歌姫またはカスタムディレクティブ(この場合、金額を入力するためのディレクティブ)でハングしている場合、データは「 そのまま$ viewModelにロールされます。これにより、maxlengthなどのフィルターディレクティブでエラーが発生します。maxlengthは、数値にない.lengthプロパティを使用します。

Plunkerの実例

ヒント:数値で機能するすべてのカスタムディレクティブには、数値を文字列に変換するための適切なフォーマッターが必要です。

結果:


その結果、テストの約6回の反復が実行され、54の問題が見つかりましたが、それらのほとんどは同様の性質を備えていました。 問題の一部がまったくなかった可能性があります。作業のバグや文書化されていない機能のために一部のコードセクションを使用しないでください。 リファクタリングと新しい検証への切り替えを考慮して、合計56のコミットと1人あたり3週間の作業時間が移行に費やされました。


パート1:重大な変更


私はすぐにIE8のサポートを失いつつあることに注意します。 ただし、必要なポリフィルを接続することで返すことができます。

Work $ parse


.bind、.call、.apply


式(たとえば{{}} )内で.bind.call、および.applyを呼び出すことはできなくなりました。

これにより、既存の関数の動作を変更できないことを確認できます。

__proto__


バージョン1.3から、 非推奨の __proto__プロパティが削除れました。

以前は、グローバルプロトタイプにアクセスするために使用できました。

対象


式内でオブジェクトを使用することは禁止されています。

これは、式で任意のコードを実行できるためです。
例:
 ''.sub.call.call( ({})["constructor"].getOwnPropertyDescriptor(''.sub.__proto__, "constructor").value, null, "alert('evil')" )() 


誰かがObject.keyまたは別のメソッドを必要とする場合、 scopeを介してスローします

{定義、ルックアップ} {ゲッター、セッター}


バージョン1.3以降、 {define、lookup} {Getter、Setter}プロパティは禁止されており、式内で任意のコードを実行できます。

これらのプロパティが必要な場合は、それらをコントローラでラップし、手でスコープにロールします。

$ parseProvider

[コミット]

廃止された$ parseProvider.unwrapPromisesおよび$ parseProvider.logPromiseWarningsメソッドを削除しました。

$補間する


$ interpolateによって返される関数には、 .parts配列が含まれなくなりました。 代わりに、以下が含まれます


toBoolean


格納庫では、真実を確認するために、独自のtoBoolean()の実装を使用します。これは、次の行の形式でいくつかの非標準値をfalseと同等にします

'f''0''false''no''n''[]'

バージョン1.3以降、通常のJSと同じ値のみがfalseに設定されます。

falsenullundefinedNaN0「」

habrでそれについて書いた

例:
  $scope.isEnabled = 'no'; 

<1.3:

  {{ isEnabled ? ' ' : '' }} 

1.3+:

  {{ isEnabled ? '' : ' ' }} 




ヘルパー


.copy()


以前は、オブジェクトを操作するとき、 コピーは、プロトタイプにあるものを含むオブジェクトのすべてのプロパティをコピーし、プロトタイプチェーン(Date、RegExp、およびArrayを除く)が失われました。

バージョン1.3以降、独自のプロパティ( hasOwnPropertyによるソートのようなもの)のみをコピーし、元のプロトタイプを参照します。

例:
  var Foo = function() {}; Foo.prototype.bar = 1; var foo = new Foo(); var fooCopy = angular.copy(foo); foo.bar = 3; 

<1.3:

  console.log(foo instanceof Foo); // => true console.log(fooCopy instanceof Foo); // => false console.log(foo.bar); // => 3 console.log(fooCopy.bar); // => 1 

1.3+:

  console.log(foo instanceof Foo); // => true console.log(fooCopy instanceof Foo); // => true console.log(foo.bar); // => 3 console.log(fooCopy.bar); // => 3 

IE8: Polyfill Object.createおよびObject.getPrototypeOfが必要

.forEach()


以前は、列挙の過程で配列が増加した場合、サイクルは新しく出現した要素も通過しました。

バージョン1.3以降、配列内の要素の数をキャッシュし、それらに沿ってのみ渡します。ここでは、ネイティブのArray.forEachに近づきました

例:
  var foo = [1, 2]; 


<1.3:

  angular.forEach(foo, function (value, key) { foo.push(null); // =>     null    console.log(value); }); 

1.3+:

  angular.forEach(foo, function(value, key) { foo.push(null); // =>  1,  2    console.log(value); }); 


.toJson()


このヘルパーの本質は、主にすべてのデータをシリアル化するのではなく、特殊文字$で始まらないデータのみをシリアル化することです。

バージョン1.3以降、名前が$$で始まるプロパティのみをシリアル化しません。

例:
  var foo = {bar: 1, baz: 2, $qux: 3}; 

<1.3:

  angular.toJson(value); // => {"bar": 1} 

1.3+:

 angular.toJson(value); // => {"bar": 1, "$bar": 2} 




jqLit​​e


主なものについて簡単に:

選択してください


コントローラー


SelectControllerはSelectディレクティブとngOptionsディレクティブの抽象化の1つです。

これは、 ngOptionsSelectから削除できるようになったことを意味します。

Selectディレクティブのさまざまなバリエーションには、独自のメソッドSelectController.writeValueおよびSelectController.readValueがあり 、これらは$ viewValueタグ<select>およびその子<option>の操作を担当します。

ngOptionsの値


以前のngOptionsでは、代理キーは転送されたコレクションのインデックスまたはアイテムキーを使用していました。

バージョン1.4以降、コレクション内のこのアイテムに対してhashKey呼び出しが使用されます。

したがって、 DOMから値を直接読み取ると、問題が発生する可能性があります。

例:
 <select ng-model="model" ng-option="i in items"></select> 

<1.4:

  <option value="1">a</option> <option value="2">b</option> <option value="3">c</option> <option value="4">d</option> 

1.4以降:

  <option value="string:a">a</option> <option value="string:b">b</option> <option value="string:c">c</option> <option value="string:d">d</option> 


ngModelとオプション値の比較


バージョン1.4以降、 selectディレクティブは、厳密な比較を使用してoptionngModelの値の比較を開始します

これは、値1が falseまたはtrueの値と同等ではないのと同じように、値1「1」と同等ではないことを意味します
モデルに値1を入力すると、 不明なオプションが表示されます

これを回避するには、モデルにscope.model = "1"などの文字列を配置する必要があります。

モデルに正確な数値が必要な場合は、フォーマッターとパーサーを使用した変換を使用することをお勧めします。

例:
 ngModelCtrl.$parsers.push(function(value) { return parseInt(value, 10); //    }); ngModelCtrl.$formatters.push(function(value) { return value.toString(); //    }); 


仕分け


ngRepeatの場合のように 、アルファベット順のソートは機能しなくなりましたが、 Object.keys(obj)の呼び出しに順番に対応しています。


ngRepeat


仕分け

[コミット] [問題] [聖戦]

以前は、ngRepeatは、オブジェクトをソートし、キーでアルファベット順にソートしていました。 バージョン1.4からは、あたかもobjのキーを反復処理しているかのように、ブラウザ固有の順序でそれを返します。

これは、キーが削除または再インストールされない限り 、通常、ブラウザが宣言された順序でオブジェクトのキーを返すためです。

オブジェクトを反復処理するには、オブジェクトを配列に変換するカスタムフィルターを使用することをお勧めします。


$コンパイル


controllerAs、bindToController


バージョン1.3では、 bindToControllerが導入されました。 バージョン1.4以降、オブジェクトを渡して、分離されたスコープを指定できます。

この点で、コントローラーコンストラクターから返されたオブジェクトはスコープを上書きします。

controllerAs構文を使用するビューは、関数自体への参照を受け取らず、関数が返すオブジェクトへの参照を受け取ります。

bindToControllerがディレクティブで使用されている場合、以前のすべてのバインディングが新しいコントローラーに再インストールされ、インストールされているすべてのウォッチャーが削除されます( unwatch )。

孤立したスコープ内の式「&」


以前は、式を持つ属性が存在しない場合でも、関数は常に式で作成されていました(この場合、 undefinedを返す関数が作成されました)。

1.4以降、 @の動作は@に近づきました。 式が欠落している場合、$スコープ内の対応するメソッドも欠落しています。 アクセスすると、 undefinedを返す関数の代わりにundefinedを取得します。

置換ディレクティブのプロパティ


1.3からは非推奨になり、次のメジャーリリースで削除する必要があります。

これは、属性のマージに問題があるという事実によって説明されます。

詳細:
組み合わせた場合

  <div ng-class="{hasHeader: true}"></div> 



  <div ng-class="{active: true}"></div> 

それから

  <div ng-class="{active: true}{hasHeader: true}"></div> 

式が無効であるという対応するエラーがあります。

また、このような指令のカプセル化レベルが一般的で不十分です。

このテーマに関するHolivarはこちらから入手できます

$オブザーバー


バージョン1.3から、属性オブザーバーを削除する便利な方法がようやく得られました。attr.observeが呼び出されると、destructor関数が(watchと同様に)戻ります。 以前、彼はオブザーバーの機能へのリンクを返しました。

さて、オブザーバーの機能へのリンクを作成するには、まずどこかに保存する必要があります。

例:
<1.3:

 directive('directiveName', function() { return { link: function(scope, elm, attr) { var observer = attr.$observe('someAttr', function(value) { console.log(value); }); } }; }); 

今のように:

 directive('directiveName', function() { return { link: function(scope, elm, attr) { var observer = function(value) { console.log(value); }; var destructor = attr.$observe('someAttr', observer); destructor(); //   } }; }); 


外部スコープアクセス


孤立したディレクティブが定義されている要素の属性を介して、孤立したスコーププロパティを取得できなくなりました。

例:
次のディレクティブが指定されています。

  app.controller('testController', function($scope) { $scope.controllerScope = true; }); app.directive('testDirective', function() { return { template:'<span ng-if="directiveScope">world!</span>', scope: {directiveScope: '='}, controller: function($scope) {}, replace: true, restrict: 'E' } }); 

<1.3:

  Hello <test-directive directive-scope="controllerScope"></test-directive> // Hello 

1.3+:

  Hello <test-directive directive-scope="controllerScope"></test-directive> // Hello world! 




ngModelController


$ setViewValue()


$ setViewValue()の動作が少し変更され、以前のように$ modelValueの変更がすぐにスローされなくなりました。

これで、モデルは2つのngModelOptions設定に応じて更新され、特に次のようになります。


デフォルトでは、 updateOndefaultであり、 debounce0であるため、$ modelValueは以前と同様に即座に実行されます。
ただし、古いコードを使用する場合は、上記の機能を検討する価値があります。

$ commitViewValue



updateOndebounceを無視して、任意のコストで即座に$ modelValueを更新する場合は、 $ commitViewValue()を使用します。

$ commitViewValue()は引数を取りません。 彼は以前、文書化されていない再検証引数を使用していました
$$ lastCommittedViewValueであっても、強制的に再検証と関連プロセスを開始するためのハックとしてのプライベートAPI
更新されていませんが、最近のバージョンでは削除されています。

$ cancelUpdate()


$ rollbackViewValue()に名前が変更されました。

この呼び出しにより、 $ viewValueを状態$$ lastCommittedViewValueに「ロールバック」し、進行中のすべてのデバウンスをキャンセルして、ビュー(入力など)を再描画できます。

例:
<1.3:
 $scope.resetWithCancel = function (e) { $scope.myForm.myInput.$cancelUpdate(); $scope.myValue = ''; }; 


1.3+:
 $scope.resetWithCancel = function (e) { $scope.myForm.myInput.$rollbackViewValue(); $scope.myValue = ''; }; 


入力:日付、時刻、現地時間、月、週


バージョン1.3以降 Angularは通常、数値に関連するHTML5入力をサポートしています。

このような入力のngモデルでは、 Dateオブジェクトは厳密に

これらの入力をサポートしていない古いブラウザーでは、ユーザーにはテキストが表示されます。 そのような場合、必要な日付に正しいISO形式を入力する必要があります。


検証


$エラー収集

[コミット]

以前は、 $ setValidityを使用してコントロールの有効性を手動で制御することにより 、任意のプロパティを$エラーに格納できました。

バージョン1.3以降、最終的な検証は、 $エラーハッシュが空かどうかによって異なります。 ngModelCtrlでプロパティをスロー$手動でエラーが発生し、そこから時間内に削除しないと、このプロパティの値に関係なく、永久に無効なコントロールが取得されます。

$ setValidityになります

[コミット]

$ setValidityを使用すると、 nameresultの 2つの引数を受け入れることにより、特定のコントロールプロパティの有効性を設定できます

以前は、 結果は 、渡された内容に関係なく、常にtrueまたはfalseにキャストされていました。

バージョン1.3以降、 $ setValidityは、 resultに渡されるfalseundefinednullを区別し始めます結果が正確にブール値であることを確認する価値があります。

たとえば、非同期バリデーターの内部では、 undefinedおよびnullの値が使用されます。 そのため、すべての同期バリデーターが有効ではない場合、非同期のバリデーターの値はnullに設定されます 。 同期バリデーターの準備ができており、非同期検証が開始されている場合、 保留中がある限り、バリデーター値はundefinedに設定されます。

$パーサーと未定義。

[コミット]

以前は、たとえば、それを壊したい場合、 $パーサーチェーンにundefinedをスローできました。

バージョン1.3以降、パーサーは未定義の処理をせず、 {parse:true}$ errorに設定することで制御を無効にしなくなりました

これは、 $ viewValueまだ設定されていない )のときにパーサーが起動しないようにするために行われます

ngPattern

[コミット]

1.4.5以降、 ngPatternディレクティブは、 $ parseValuesチェーンが機能する前に、 $ viewValue (以前は$ modelValueに基づいていた)に基づいて検証します。

これは、パーサーが$ viewValueをそれぞれDateおよびNumberに変換するために、 入力[日付]および入力[番号]が検証されない場合の問題が原因です。

このディレクティブで$ viewValue修飾子を使用し、以前のように正確に$ modelValueをチェックする必要がある場合は、カスタムディレクティブを使用する必要があります。

例:
  .directive('patternModelOverwrite', function patternModelOverwriteDirective() { return { restrict: 'A', require: '?ngModel', priority: 1, compile: function() { var regexp, patternExp; return { pre: function(scope, elm, attr, ctrl) { if (!ctrl) return; attr.$observe('pattern', function(regex) { /** * The built-in directive will call our overwritten validator * (see below). We just need to update the regex. * The preLink fn guaranetees our observer is called first. */ if (isString(regex) && regex.length > 0) { regex = new RegExp('^' + regex + '$'); } if (regex && !regex.test) { //The built-in validator will throw at this point return; } regexp = regex || undefined; }); }, post: function(scope, elm, attr, ctrl) { if (!ctrl) return; regexp, patternExp = attr.ngPattern || attr.pattern; //The postLink fn guarantees we overwrite the built-in pattern validator ctrl.$validators.pattern = function(value) { return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); }; } }; } }; }); 


ngMinlength / ngMaxlength


1.3以降、 ngMinlengthおよびngMaxlengthディレクティブは、 $ viewValue (以前は$ modelValueに基づいていました )に基づいて検証を実行します。

これにより、これらのディレクティブを、電話に入るためのマスクなど、 $ viewValueを変更するディレクティブと一緒に使用すると、誤った検証につながる可能性があります。

問題を回避するには、2つの解決策があります。

  1. $ viewValueに従って最大文字数を変更します (たとえば、「xx-xx」という形式のマスク、モデルに「xxxx」のみが含まれる場合、以前のように4ではなくmaxlength =「5」として考慮される必要があります)
  2. $ modelValueをチェックする独自のカスタムディレクティブを使用します。 ただし、仕様によると、入力される文字数が制限されるため、 maxlengthに問題がある可能性があります。そのため、制限を実装する必要があります。


ほとんどの場合、最初のオプションを最も問題の少ないものとして使用することをお勧めします。
2番目のオプションは、minLengthに役立ちます。 事前にn文字が入力されているマスク付きのオプション入力がある場合(たとえば、「+ 7」が設定された電話の入力)、これは、minLengthが空である限りフィールドを検証しないためです。

カスタムmaxlengthの例
  (function (angular) { 'use strict'; angular .module('mainModule') .directive('maxModelLength', maxlengthDirective); function maxlengthDirective () { return { restrict: 'A', require: '?ngModel', link: function (scope, elm, attr, ctrl) { if (!ctrl) { return; } var maxlength = -1; attr.$observe('maxModelLength', function (value) { var intVal = parseInt(value); maxlength = isNaN(intVal) ? -1 : intVal; ctrl.$validate(); }); ctrl.$validators.maxlength = function (modelValue, viewValue) { return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (String(modelValue).length <= maxlength); }; /* *  ,      - maxlength *      ,      * */ elm.bind('keydown keypress', function (event) { var stringModel = String(ctrl.$modelValue); if (maxlength > 0 && !ctrl.$isEmpty(ctrl.$modelValue) && stringModel.length >= maxlength) { if ([8, 37, 38, 39, 40, 46].indexOf(event.keyCode) === -1) { event.preventDefault(); } } }); } }; } })(angular); 


カスタム最小長の例
  (function (angular) { 'use strict'; angular .module('mainModule') .directive('minModelLength', minlengthDirective); function minlengthDirective () { return { restrict: 'A', require: '?ngModel', link: function (scope, elm, attr, ctrl) { if (!ctrl) { return; } var minlength = 0; attr.$observe('minModelLength', function (value) { minlength = parseInt(value) || 0; ctrl.$validate(); }); ctrl.$validators.minlength = function (modelValue, viewValue) { return ctrl.$isEmpty(modelValue) || String(modelValue).length >= minlength; }; } }; } })(angular); 


仮想ngModel問題:

直接データ入力を目的としない要素(たとえば、ルートngModelで動作する複数の入力を含むディレクティブのルート)でngMinlength / ngMaxlengthを使用し、数値データを使用すると、誤ったデータ検証を受け取ります(常にエラーが発生します) 。

より具体的には、 数値は常に$ viewValueに格納されます 。これは、バリデーターがチェックできないためです。 .lengthを取得できません。

なぜこれが起こっているのですか?
$ Compileには基本的なディレクティブが含まれています。 たとえば、 inputおよびtextareaの場合、 inputDirectiveコントロールが呼び出されます 。これはattr.typeを受け取り、それに基づいてinputTypeコレクションの関数の1つを呼び出します。 テキスト型の場合、これはそれぞれtextInputTypeであり、制御をstringBasedInputType関数にスローします 。この関数は、値を$ formattersの文字列に変換するためのコードを追加します。

ngModelが inputなどの既存の基本要素に関連付けられていないが、たとえば単純なdivまたはカスタムディレクティブでハングする場合、データは文字列への追加の変換なしで$ viewModelにそのまま 」ロールされ、ディレクティブのエラーが発生します。 ngMaxlengthのようなフィルター

, , , .

Plunker .



スコープとダイジェスト


$ id


今では意味があります。

以前は、数値がスコープ計算に十分でない可能性があるという懸念のために、文字列を使用して$ idを示すことにしました(実際には['0'、 '0'、 '0']の形式の配列です)が、これに関する懸念があります法案は実現しませんでした。

代わりに、多くのスコープを作成するとき(たとえば、大きなテーブルを操作するとき)に余分な負荷(数ミリ秒追加)が発生しましたプライムに切り替えると、この問題が解決します。

例:
<1.3: [ Plunker]

 console.log($rootScope.$id); // => 001 

1.3+: [ Plunker]

 console.log($rootScope.$id); // => 1 


放送して放出する


イベントが配布チェーンの最後に到達したらすぐに、currentScopeをnullに設定します。

これは、誰かが非同期関数からイベントにアクセスしようとしたときにevent.currentScopeが誤って使用された場合の追跡が難しいバグが原因です
以前は、この場合のevent.currentScopeはチェーンの最後の$スコープ等しく、静かにアプリケーションの誤動作を引き起こしていましたevent.currentScope
を使用する場合の同様のケースは、エラーが発生します。event.currentScopeに

非同期的にアクセスするには、event.targetScopeを使用する必要があります

例:
scope:

  001 ($rootScope) └ 002 ($scope of ParentCtrl) └ 003 ($scope of ChildCtrl) └ 004 ($scope of GrandChildCtrl) 

customEvent GrandChildCtrl

<1.3: [ Plunker]

  .controller('ParentCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope); // $id  002 $timeout(function() { console.log(event.targetScope) // => $id  004 console.log(event.currentScope) // => $id  001 }); }) }) .controller('ChildCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope); // $id  003 $timeout(function() { console.log(event.targetScope) // => $id  004 console.log(event.currentScope) // => $id  001 }); }) }) 

1.3+: [ Plunker]

  .controller('ParentCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope); // $id  2 $timeout(function() { console.log(event.targetScope) // => $id  4 console.log(event.currentScope) // => null }); }) }) .controller('ChildCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope); // $id  3 $timeout(function() { console.log(event.targetScope) // => $id  4 console.log(event.currentScope) // => null }); }) }) 




httpおよびリソース


JSONプリミティブ

[コミット]

バージョン1.3以降、プリミティブを含むContent-Type:application / jsonの応答はJSONとして解析され始めます。

一般に、これはバグ修正です。これにより、回答を操作するときに松葉杖を回避できますが、場合によっては既存のコードが破損する可能性があります。

例:
«OK» , , .

<1.3:

  response === 'OK' // => false response === '"OK"' // => true 

1.3+:

  response === 'OK' // => true response === '"OK"' // => false 


$ http transformRequest


バージョン1.4以降、transformRequest関数はサポートされなくなり、リクエストヘッダーは変更されません。代わりに、要求パラメーターのheadersプロパティと、目的のヘッダーに対応するgetter関数を使用する価値があります。config

オブジェクトは、関数の最初の引数のふりをします。これにより、ヘッダーを動的に定義および設定できます。

例:
'X-MY_HEADER'

<1.4:

  function requestTransform(data, headers) { headers = angular.extend(headers(), { 'X-MY_HEADER': 'test' }); return angular.toJson(data); } 

1.4+:

  $http.get(url, { headers: { 'X-MY_HEADER': function(config) { return 'test'; } } }) 


$ HTTPインターセプター


$ httpProviderresponseInterceptorsコレクションすでに非推奨のステータスになっており、2つの異なるAPI(1つは完全に明らかではない)を持っていたため、さまざまな困惑を招きました。バージョン1.3以降、このコレクションとその機能は[削除]されています。代わりに、インターセプターを登録するための新しい透過的なAPIが利用可能です。





例:
myHttpInterceptor .

< 1.3: [ Plunker]

  $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { return function(promise) { return promise.then(function(response) { //  success return response; }, function(response) { //  error if (canRecover(response)) { return responseOrNewPromise } return $q.reject(response); }); } }); $httpProvider.responseInterceptors.push('myHttpInterceptor'); 

1.3+: [ Plunker]

  $provide.factory('myHttpInterceptor', function($q) { return { response: function(response) { //  success return response; }, responseError: function(response) { //  error if (canRecover(response)) { return responseOrNewPromise } return $q.reject(response); } }; }); $httpProvider.interceptors.push('myHttpInterceptor'); 


$ httpBackendおよびJSONP


これで、角度は「成功」イベントでエラーをキャッチし、JSONPの空の回答(コールバックでデータが欠落)はエラーになりません(以前にエラーを生成し、ステータスを-2に設定)。

次のことを知っている(または知らない)のは興味深いことです。
onload onerror JSONP script .
jQuery event .

, , .

$.data(«events») , jqLite .

IE8:onreadystatechangedイベントはサポートされなくなりました

$リソース


$リソースインスタンスtoJson()を呼び出すと、単一の$で始まるすべてのプロパティのように、シリアル化中に以前に切り取られ$ promiseおよび$ resolveプロパティが含まます上記のtoJson()の変更による$で始まるプロパティはシリアル化されなくなりました。現在、$$で始まるプロパティのみシリアル化されます。これに基づいて、シリアル化された$リソースにはこれらのプロパティが含まれると予想されますが、そうではありません。具体的には、はシリアル化中にこれら2つのプロパティを切り取ります。





ユーザーが追加したプロパティを含む他のすべてのプロパティはシリアル化され、結果のjsonに含まれます。


$インジェクト


モジュール:.config()および.provider()


以前は、.provider()が機能する前に.config()を呼び出すことが可能でした。

バージョン1.3以降、この動作は不可能で、すべての.provider()モジュールが機能した後にのみ.config()が呼び出されます。

例:
  app .provider('$rootProvider1', function() { console.log('Provider 1'); this.$get = function() {}; }) .config(function() { console.log('Config'); }) .provider('$rootProvider2', function() { console.log('Provider 2'); this.$get = function() {}; }); 

<1.3: [ Plunker]

Provider 1, Config, Provider 2

1.3+: [ Plunker]

Provider 1, Provider 2, Config



ngAnimate


すべての方法


以前は、すべての$ animateメソッドの最後の引数はdoneコールバックで、アニメーションが終了したときに実行されていました。
バージョン1.3以降、オプションスタイルのセットがそこ渡され、要素に適用されます。完了する

代わりにすべての関数がpromiseを返すようになりましたその解決はアニメーションの完了を意味します。

animate.enter()およびanimate.move()


これらのメソッドには4つの引数(要素、親、後、オプション)があります。

以前は、after引数が指定されなかった場合、指定されたelementの後に新しい要素が追加され、指定された場合、afterの後に追加されまし

問題は、同様のAPIで、コンテナの上部に新しい要素を追加できないことです。

開始バージョン1.3で引数がされている場合、後に指定されていない場合、この要素は、コンテナの上部に追加された親

したがって、新しい要素を挿入する特定の要素を指定する必要があります。

例:
$animate.enter

<1.3:

  //     `element` `$animate.enter(element, parent);` 

1.3+:

  //      `parent` `$animate.enter(element, parent);` //     `element` `$animate.enter(element, parent, angular.element(parent[0].lastChild));` 




フィルター


内部コンテキスト

[コミット]

そのコントロールの内部状況:以前は、すべてのフィルタは、文書化されていない機能を持っている、これは呼ば$スコープフィルターがトリガーされました、。

これができない理由と正しい方法:
, , $scope . , ng-repeat .

Andy Joslin's :

  yourModule.filter("as", function($parse) { return function(value, path) { return $parse(path).assign(this, value); }; }); 

1.3 , $scope ( this ) undefined .

$scope , ** **, $scope 10%, $scope $digest
Error: 10 $digest() iterations reached. Aborting!

:

  • : , .
  • $scope .

ng-repeat:

item in (filterResults = (items | filter:query)) , , as : item in items | filter:query as filterResults) .

フィルター


現在では、配列でのみ機能します。

バージョン1.4ではオブジェクトでフィルターを呼び出そうとするとエラーが発生します。以前は、単に「サイレント」に空の配列を返しました。

オブジェクトを反復処理するには、オブジェクトを配列に変換するカスタムフィルターを使用することをお勧めします

limitTo


以前は、無効な制限がlimitToに渡された場合(たとえば、undefined)、空の配列または文字列を返していました。

バージョン1.4以降、制限が正しくない場合、元の入力データ、つまり フィルタを適用しない配列または文字列。


ngCookies


$クッキー


バージョン1.4以降、ブラウザCookieは$ cookiesサービスオブジェクトコピーされなくなり、データはセッター/ゲッターではなく、値を操作するためのより透明なAPIを介して処理されます。


これは、オブジェクトに関連データが含まれなくなったときのデータ同期バグによるものです。つまり、ウォッチャーを使用してオブジェクトを介したCookieの変更を追跡することはできなくなります。

このような操作は、たとえば、ブラウザーのタブ間の通信のために過去に必要でしたが、最近では、たとえばlocalStorageなどのより便利なツールがあります

$ cookieStore


バージョン1.4以降、$ cookieStoreサービス非推奨ステータス受け取り、すべての便利なロジックが$ cookieサービスに転送され$ cookieStoreにアクセスすると現在$ cookieインスタンスが返さます


結論:


プロジェクトが、フレームワークのバグと文書化されていない機能に基づいて、自明でないソリューションの松葉杖を過剰に使用しない限り、移行に問題はありません
アニメーションやアプリケーションの検証に角度の手段を使用しなかった人にとっては、まったく痛みはありません。

次の記事では、新しいバージョンのすべての利点と、それらの助けを借りて生産性を向上させる方法について説明します。

移行中に他の興味深い問題に遭遇した場合は、コメントで共有してください。

Source: https://habr.com/ru/post/J268265/


All Articles