バージョン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になってから属性が変更され、新しい属性ではその逆になります。
ヒント:- ng-show / ng-hideで初期化する際に重要な動的ディレクティブを保存しないでください。これにはng-ifを使用してください。 このヒントはバージョン1.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;
非同期検証はより困難であることが判明しました。フォームは無効でしたが、エラーは含まれていませんでした。
ここでは、マスクの機能により(
$ 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配列が含まれなくなりました。 代わりに、以下が含まれます
- .expressions 。テキスト内のすべての式が含まれます。
- .separators 。テキスト内の式の間にセパレータがあります。 結合の便宜上、常に.expressionsよりも1要素長くなります。
toBoolean
格納庫では、真実を確認するために、独自の
toBoolean()の実装を使用します。これは、次の行の形式でいくつかの非標準値を
falseと同等にし
ます 。
'f' 、
'0' 、
'false' 、
'no' 、
'n' 、
'[]'バージョン
1.3以降、通常のJSと同じ値のみがfalseに設定されます。
false 、
null 、
undefined 、
NaN 、
0 、
「」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);
1.3+: console.log(foo instanceof Foo);
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);
1.3+: angular.forEach(foo, function(value, key) { foo.push(null);
.toJson()
このヘルパーの本質は、主にすべてのデータをシリアル化するのではなく、特殊文字$で始まらないデータのみをシリアル化することです。
バージョン
1.3以降、名前が$$で始まるプロパティのみをシリアル化しません。
例: var foo = {bar: 1, baz: 2, $qux: 3};
<1.3: angular.toJson(value);
1.3+: angular.toJson(value);
jqLite
主なものについて簡単に:
- データをテキストおよびコメントノードに設定できなくなりました これは、メモリリークと浄化hemoによるものです。
- element.detach()を呼び出しても、$ destroyイベントはトリガーされません。
選択してください
コントローラー
SelectControllerは 、
Selectディレクティブと
ngOptionsディレクティブの抽象化の1つです。
これは、
ngOptionsが
Selectから削除できるようになったことを意味します。
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ディレクティブは、厳密な比較を使用して
optionと
ngModelの値の比較を開始し
ます 。
これは、値
1が falseまたは
trueの値と同等ではないのと同じように、値
1が
「1」と同等ではないことを意味し
ます 。
モデルに値
1を入力すると、
不明なオプションが表示されます 。
これを回避するには、モデルに
scope.model = "1"などの文字列を配置する必要があります。
モデルに正確な数値が必要な場合は、フォーマッターとパーサーを使用した変換を使用することをお勧めします。
例: ngModelCtrl.$parsers.push(function(value) { return parseInt(value, 10);
仕分け
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設定に応じて更新され、特に次のようになります。
- updateOn :このトリガーで指定されたイベントのいずれかが呼び出されるまで、モデルは更新されません
- デバウンス : デバウンスのいずれかで指定された時間が経過するまで、モデルは更新されません
デフォルトでは、
updateOnは
defaultであり、
debounceは
0であるため、$ modelValueは以前と同様に即座に実行されます。
ただし、古いコードを使用する場合は、上記の機能を検討する価値があります。
$ commitViewValue
updateOnと
debounceを無視して、任意のコストで即座に$ 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を使用
すると、
nameと
resultの 2つの引数を受け入れることにより、特定のコントロールプロパティの有効性を設定でき
ます 。
以前は、
結果は 、渡された内容に関係なく、常に
trueまたは
falseにキャストされていました。
バージョン
1.3以降、
$ setValidityは、
resultに渡される
false 、
undefined 、
nullを区別し始め
ます 。
結果が正確にブール値であることを確認する価値があります。
たとえば、非同期バリデーターの内部では、
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) { if (isString(regex) && regex.length > 0) { regex = new RegExp('^' + regex + '$'); } if (regex && !regex.test) {
ngMinlength / ngMaxlength
1.3以降、
ngMinlengthおよび
ngMaxlengthディレクティブは、
$ viewValue (以前は
$ modelValueに基づいてい
ました )に基づいて検証を実行します。
これにより、これらのディレクティブを、電話に入るためのマスクなど、
$ viewValueを変更するディレクティブと一緒に使用すると、誤った検証につながる可能性があります。
問題を回避するには、2つの解決策があります。
- $ viewValueに従って最大文字数を変更します (たとえば、「xx-xx」という形式のマスク、モデルに「xxxx」のみが含まれる場合、以前のように4ではなくmaxlength =「5」として考慮される必要があります)
- $ 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); }; 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']の形式の配列です)が、これに関する懸念があります法案は実現しませんでした。代わりに、多くのスコープを作成するとき(たとえば、大きなテーブルを操作するとき)に余分な負荷(数ミリ秒追加)が発生しました。プライムに切り替えると、この問題が解決します。放送して放出する
イベントが配布チェーンの最後に到達したらすぐに、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);
1.3+: [ Plunker] .controller('ParentCtrl', function($scope, $timeout) { $scope.$on('customEvent', function(event) { console.log(event.currentScope);
httpおよびリソース
JSONプリミティブ
[コミット]バージョン1.3以降、プリミティブを含むContent-Type:application / jsonの応答はJSONとして解析され始めます。一般に、これはバグ修正です。これにより、回答を操作するときに松葉杖を回避できますが、場合によっては既存のコードが破損する可能性があります。例:«OK» , , .
<1.3: response === 'OK'
1.3+: response === 'OK'
$ 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インターセプター
$ httpProviderのresponseInterceptorsコレクションはすでに非推奨のステータスになっており、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) {
1.3+: [ Plunker] $provide.factory('myHttpInterceptor', function($q) { return { response: function(response) {
$ 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 21.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:
1.3+:
フィルター
内部コンテキスト
[コミット]そのコントロールの内部状況:以前は、すべてのフィルタは、文書化されていない機能を持っている、これは呼ば$スコープフィルターがトリガーされました、。これができない理由と正しい方法:, ,
$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 $digestError: 10 $digest() iterations reached. Aborting! 。
: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を介して処理されます。- 得る
- 置く
- getObject
- putObject
- getAll
- 取り除く
これは、オブジェクトに関連データが含まれなくなったときのデータ同期バグによるものです。つまり、ウォッチャーを使用してオブジェクトを介したCookieの変更を追跡することはできなくなります。このような操作は、たとえば、ブラウザーのタブ間の通信のために過去に必要でしたが、最近では、たとえばlocalStorageなどのより便利なツールがあります。$ cookieStore
バージョン1.4以降、$ cookieStoreサービスは非推奨ステータスを受け取り、すべての便利なロジックが$ cookieサービスに転送され、$ cookieStoreにアクセスすると現在$ cookieインスタンスが返されます。
結論:
プロジェクトが、フレームワークのバグと文書化されていない機能に基づいて、自明でないソリューションの松葉杖を過剰に使用しない限り、移行に問題はありません。アニメーションやアプリケーションの検証に角度の手段を使用しなかった人にとっては、まったく痛みはありません。次の記事では、新しいバージョンのすべての利点と、それらの助けを借りて生産性を向上させる方法について説明します。移行中に他の興味深い問題に遭遇した場合は、コメントで共有してください。