最初の部分では、新しいバージョンへの移行の主な問題をすべて検討しましたが、これで何をしたのかについて触れます。
前述のように、移行の主な理由は、アプリケーションの速度が大幅に向上したことです:Jeff CrossとBrian FordがngEurope会議で述べたように、DOMでの操作が4.3倍、 $digest
サイクルが3.5倍(1.2と比較) 。
ただし、この速度は主に内部の最適化と魔法によるものではなく、コードをより効率的に記述できるツールの提供によるものです。
これらのツールを見てみましょう!
デバッグ情報
アンギュラーがリソースの大部分を、クラスの追加などのデバッグを容易にする情報に費やしていることはニュースではありません
DOM要素( ng-binding
およびng-isolated-scope
クラスなど)またはそれらにさまざまなメソッドをアタッチしてscope
にアクセスしscope
(たとえば.scope()
および.isolateScope()
)。
これらはすべて、分度器やバタランなどのツールの作業に役立ち、必要ですが、このデータは生産に必要ですか?
バージョン1.3以降、デバッグ情報を無効にできます。
app.config(['$compileProvider', function ($compileProvider) { $compileProvider.debugInfoEnabled(false); }]);
しかし、製品を販売する必要があり、デバッグが無効になっている場合はどうでしょうか?
ここでは、 angular
オブジェクトの.reloadWithDebugInfo()
メソッドが保存され、 angular
オブジェクトはグローバルなので、コンソールからこのコードを簡単に実行できます。
angular.reloadWithDebugInfo();
$ applyAsync
バージョン1.3では、 $applyAsync
サービスが$applyAsync
れました。多くの点で、そのメカニズムは既存の$evalAsync
サービスと似ています。
簡単に言えば、式をキューに追加してから待機し(setTimeout(...、0)を設定し、最新のブラウザーでは約10ミリ秒です)、過去に別の式がキューに追加されていない場合は、開始します
$rootScope.$digest()
。
これにより、同じ$ダイジェストループで実行され、$ applyを頻繁に呼び出すことを心配せずに、DOMに影響する多くの同時式を実行できます。
$ evalAsyncとの違いは何ですか?
主な違いは、 $applyAsync
自体がダーティチェックの前に$digest
サイクルの開始時に式キュー全体を実行することです。これにより、 $evalAsync
はダーティチェック中に実行され $evalAsync
(より正確には、 (ダーティチェックサイクルの開始)、およびキューに追加された式($ウォッチの外部)は、$ダイジェストサイクルを再び開始します。これにより、同じ$digest
式が繰り返し実行されます。
ただし、このツールを使用することの本当の利点は、角度の内部サービス、たとえば$httpProvider
ます。
$ http
$http
サービスとXHRリクエストを行う他の方法との主な違いは、完了時に$apply
呼び出すことです。
問題は多くの並列クエリであり、それぞれが完了時に$apply
し、ブレーキにつながります。
この問題は、バージョン1.3の $applyAsync
の到着により解決されました。
app.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
このコードには、 $applyAsync
内での$httpProvider
$applyAsync
使用が含まれ$applyAsync
。
これにより、同時要求のすべての約束がresolved
ときに1回だけ$ applyを実行でき、パフォーマンスが大幅に向上します。
一度バインド
AngularJSの主なパフォーマンスの問題の1つは、双方向のバインディングがすべての式に適用されますが、すべてのデータがそれを必要とするわけではないという事実に起因する$watch
の膨大な量です。
静的データの場合、一晩バインドするだけで十分であり、カスタムディレクティブによってこれが決定される前に、バージョン1.3ですべてが変更されました。
バージョン1.3以降、新しい構文は、式の先頭に::
の形式で使用できます。
::
始まる式は一方向のバインディングとして認識され、式のデータが安定して最初の$digest
サイクルが経過するとすぐに追跡(監視解除)されなくなります。
例:
{{:: foo }} <button ng-bind=":: foo"></button> <ul> <li ng-repeat=":: foo in bar"></li> </ul> <custom-directive two-way-bind-property=":: foo"><custom-directive>
データの安定性:
データは、 undefined
限り不安定であると見なされます。 NaN
、 false
、 ''
[]
またはnull
いずれであっても、他のデータは安定していると見なされ、 unwatch
式になります。
これは、最初の$ダイジェストサイクル中に利用できない静的データに必要です。
たとえば、データがサーバーから送信undefined
れた場合、リクエストを待機している間、変数の値をundefined
ままにすることができます。 この間ずっと、この式のウォッチャーは存続し、 undefined
以外のデータをインストールした後にのみ、その結果を返します。
::
を含む式を定数として扱います。一度設定すると、変更できなくなります。
更新不可の更新:
100のうち99のケースで更新されない式があると仮定します(つまり、オーバーウォッチでそれに陥ることはありませんでした)が、場合によっては必要になります。 になる方法 バインドワンス式を強制的に更新できるかどうか疑問に思いました。
いいえ、できません:)ただし、独自の属性ディレクティブを作成して、特定のイベントに応じてディレクティブ全体を再コンパイルするように強制できます。 ここに例があります 。
パフォーマンスのこの部分は終了し、素敵な利点について話すことができます。
ngModelオプション
バージョン1.3では、 ng-model
に加えて、モデルが更新されるタイミングを担当ng-model
補助ディレクティブng-model-options
導入されました。
更新時間は2つの要因に依存します。
1) updateOn
モデルが更新される特別なイベント(イベント)。たとえば、 blur
、 click
またはある種のカスタムイベントです。 デフォルトは常にdefault
。つまり、各コントロールは独自のイベントを使用します。 {event: "default customEvent"}
イベントを追加して標準イベントを拡張するdefault
は、リストにdefault
を追加することを忘れないでください(例: {event: "default customEvent"}
。
2) debounce
-新しいデータを見越してモデルを更新するときの遅延(デフォルトは0、つまり即時)。 入力{debounce: 300}
を指定し、300ミリ秒未満の間隔で3文字を入力すると、モデル(およびさまざまなモディファイヤ/バリデータ)が一度だけ更新されます。 さらに、 debounce
をイベントと組み合わせて、各イベントの遅延を示すことができます。例: {event: "default customEvent", debounce: {default: 0, customEvent: 400}}
。
これにより、多くの自転車をなくすことができ(さよならsetTimeout / clearTimeout)、パフォーマンスが大幅に向上し( $digest
、 $watchers
すべての$watchers
の無駄な再起動が$watchers
)、また非同期検証の誤検知の数が減ります(ただし、 $http
このサービスは、リクエストをスパムしないように十分にスマートですが、安定したデータを待ちます。
しかし、さらに3つの便利なオプションがあります。
allowInvalidフラグを使用すると、値が無効な場合でも$modelValue
を設定できます (デフォルトでは、値は無効ですが、 undefined
モデルに書き込まれます。これにより、たとえば中間値を見つけることができません)
setterGetterフラグを使用すると、独自の関数をngModel
として設定できます 。これはngModel.viewValue
ngModel.modelValue
とngModel.viewValue
間の一種であり、セッターおよびゲッターとして機能します。 plunkerのライブ例 。
タイムゾーンを使用すると、時間( date
またはtime
)に関連するコントロールのタイムゾーンを設定できます。たとえば、 '+0430'
は「4時間30分GTM」を意味します。 デフォルトでは、ブラウザのタイムゾーンが使用されます。
updateOnとデバウンスを無視する
モデルを手動で記録する場合、イベントで設定された遅延を無視して、すぐに更新を実行する必要がある場合があります。 これにはngModelCtrl.$commitViewValue()
メソッドがあります。
変更をキャンセル
debounce
プロセス中にハングしているすべての変更を元に戻したい場合、ビューをモデルの現在の状態に調整するメソッド$rollbackViewValue()
(以前の$cancelUpdate()
)があります。
この機能の使用例として、公式ドキュメントには入力があり、ESCを押すと変更をロールバックできます。
検証
ユーザビリティの面での主要な改善点の1つは、フォームの検証です。
以前は、 ndModel.$formatters
およびndModel.$parsers
を介して検証メカニズムを実装する必要があり、 ndModel.$setValidity()
を介して検証結果に直接影響し、非同期チェックの実装は別の喜びでした。
イノベーションはパフォーマンスにも影響しました。
実際、DOM( $parsers
)またはモデル( $formatters
)で更新されるたびに、以前に検証されていた関数が起動され、しばしば互いの値に影響を与え、検証サイクルを再開しました。
新しいバージョンでは、モデルが変更され、このモデルにエラーがない場合( {parse: true
)にのみ検証が開始され{parse: true
。 モデル自体またはバリデーター内のコントロールの表現への影響も排除され、アプリケーションの速度にプラスの影響を与えます。
いいえ、削除されませんでした。これらは他の目的のための他のツールであり、まだ必要です。
$formatters
と$parsers
はどちらも、値を受け取り、チェーンに沿って次のハンドラー関数に渡すハンドラー関数を含む配列です。 チェーン内の各リンクは、値を変更してから渡すことができます。
$フォーマッター
モデルが変更されるたびに、 $formatters
は配列内のハンドラーを逆の順序(末尾から先頭)で$formatters
ます。 最後に渡された値は、DOMでモデルがどのように表現されるかを決定します。 言い換えると、 $formatters
はngModelCtrl.$modelValue
がngModelCtrl.$modelValue
に変換される方法を担当します。
$パーサー
コントロールがDOMから値を読み取るたびに、 $parsers
はハンドラーの配列を最初から最後まで$parsers
、チェーンに沿って値を渡します。 最後に渡された値は、モデルで値がどのように表現されるかを決定します。 言い換えると、 $parsers
はngModelCtrl.$viewValue
がngModelCtrl.$viewValue
に変換される方法を担当します。
どこで使用されていますか?
まず第一に、アングル自身が彼の作品でそれらを使用しています。 たとえば、最小長の検証を使用してコントロールを作成し、 $formatters
チェックすると、空ではないが、DOMから文字列に値を変換するラッパー関数が既に含まれていることがわかります。
上記の例は、厳密に定義された形式でのみモデルに値が入っていることを確認したい場合、ハンドラーを使用して値を前処理(サニタイズ)します。
他の2つの一般的な使用方法は、値の双方向フィルタリング(たとえば、ユーザーが入力に「10,000」を入力し、「10000」がモデルに保存され、その逆の場合)とマスクの作成(ユーザーが電話マスク「+7(000 )000-00-00 "、およびモデルに保存する" 70000000000 ")。
例からわかるように-不可欠なツール。
事実は、 ndModel.$parsers
からundefined
を返すことですndModel.$parsers
は、 ngModelCtrl.$modelValue
を公開し、 ngModelCtrl.$modelValue
をundefined
に公開し、 ngModelCtrl.$modelValue
{parse: false}
をngModelCtrl.$modelValue
障害フィールド。
この場合、バリデーター( $validators
と$asyncValidators
)は作業を開始しません。
allowInvalid
フラグをtrue
設定することにより、 ngModelOptions
この動作を無効にできtrue
。
バージョン1.3から 、便利な同期および非同期検証のためのツールが用意されました。
また、新しいバリデーターの作業はモデルの更新に関係しているため、上記のngModelOptions
にも依存していることにも言及する価値があります。
同期
同期検証の場合、新しいバージョンはndModel.$validators
コレクションを提供し、検証関数で拡張します。
バリデータ関数は、有効な値と無効な値に対してそれぞれtrue
またはfalse
を返す必要があります。
例:
ngModel.$validators.integer = function(modelValue, viewValue) { // , if (ctrl.$isEmpty(modelValue)) { return true; } if (INTEGER_REGEXP.test(viewValue)) { return true; // } return false; // };
非同期
非同期検証の場合は、 ngModelCtrl.$asyncValidators
コレクションが使用され、同じロジックが検証関数でそれを拡張します。
非同期バージョンの主な違い:
- 非同期検証は、すべての同期検証が有効な場合にのみトリガーされます
- バリデーター関数は、有効な値と無効な値に対してそれぞれ
resolve()
またはreject()
実装するプロミスのみを返す必要があります。
AngularJSの約束は、特別なサービス$q
、基本的に$q
使用する$q
$timeout
や$http
などのサービスによって生成されます。
このコレクションには、1つのバリデーターによって返される複数のプロミスが含まれていません。 つまり、検証を数回呼び出すと、以前のプロミスの結果に関係なく、バリデーターの最後の呼び出しからのプロミスのみが考慮されます。
バリデーターがプロミスを渡してから解決するまで( resolve()
またはreject()
)、 ngModelCtrl.$pending
フィールドにはバリデーター名が格納され、 ngModelCtrl.$valid
ngModelCtrl.$invalid
およびngModelCtrl.$invalid
はundefined
この機能には注意してください:検証がFormCtrl.$errors
ている限りFormCtrl.$errors
フォームは無効になりますが、 FormCtrl.$errors
Errorsにはエラーが表示されませんが、 FormCtrl.$pending
は「保留中」のバリデータがFormCtrl.$pending
。
ngModelCtrl.$asyncValidators.username = function(modelValue, viewValue) { // , if (ctrl.$isEmpty(modelValue)) { return $q.when(); } var def = $q.defer(); // $timeout(function() { if (usernames.indexOf(modelValue) === -1) { def.resolve(); // , } else { def.reject(); // , } }, 2000); return def.promise; }; ngModelCtrl.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // , return $http.get('/api/users/' + value). then(function resolved() { // , , return $q.reject('exists'); }, function rejected() { // , , return true; }); };
値修飾子を使用するフィールドで( $formatters
および$parsers
を介して)非同期検証を慎重に使用することは価値があります。これにより、複数の操作、およびフィールドの誤った検証または無効化が発生する可能性があります。
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; };
ngMessages
ngMessages
モジュールngMessages
、ページ上のメッセージの表示を容易にするように設計されています。
このモジュールはページ上のあらゆるメッセージに便利に使用できるという事実にもかかわらず、通常はフォームのエラーを表示するために使用されます。 このツールが解決する問題を理解するために、エラーを表示する古い方法の苦痛を見て、それを新しいものと比較しましょう。
比較の例として、このフォームを使用してコメントを追加します。
<form name="commentForm"> <ul class="warnings" ng-if="commentForm.$error && commentForm.$dirty"> ... </ul> <label>:</label> <input type="text" name="username" ng-model="comment.username" required> <label>:</label> <textarea name="message" ng-model="comment.message" minlength="5" maxlength="500"></textarea> </form>
検証の実装はそのままにして、必要な条件に関するメッセージの表示のみを考慮します。
- 名前フィールドは空にできません
- メッセージフィールドは5文字以上にする必要があります
- メッセージフィールドは500文字を超えることはできません
エラーは.errors
ブロックに表示されます。
古いエラー表示方法
次に、これらのフィールドのエラーを表示する方法を想像してください。
<form name="commentForm"> <ul class="warnings" ng-if="commentForm.$error && commentForm.$dirty"> <span ng-if="commentForm.message.$error.minlength"> 5 </span> <span ng-if="commentForm.username.$error.maxlength"> 500 </span> <span ng-if="commentForm.username.$error.required"> </span> </ul> <label>:</label> ... <label>:</label> ... </form>
まあ、物事がそれほど悪くない限り、そうですか?
しかし、なぜニックネームのメッセージとコメントがすべて一度に表示されるのですか? 順次表示を追加してこれを修正しましょう。
<form name="commentForm"> <ul class="warnings" ng-if="commentForm.$error && commentForm.$dirty"> <span ng-if="commentForm.message.$error.minlength && commentForm.username.$valid"> 5 </span> ... </ul> <label>:</label> ... <label>:</label> ... </form>
あなたはまだ「これはそれほど悪くない」と思いますか? ページのフィールドが2ではなく20であり、それぞれに少なくとも5つの投稿があると想像してください。 同様のスタイルで、私たちのページはすぐに状況が変わるとゴミ箱に変わります。
もちろん、このタスクを実装するためのベストプラクティス、 FormController
の動作を拡張する特別なディレクティブおよび松葉杖がありFormController
(たとえば、プロジェクトでは、現在のエラーを格納するすべてのコントロールがshowError
プロパティで拡張されました) 。
新しいエラー表示方法
ngMesssages
別のモジュールとして提供されており、作業を開始する前に接続する必要があります。 パッケージマネージャーを使用してモジュールをダウンロードまたはインストールし、プロジェクトに接続します。
<script src="path/to/angular-messages.js"></script>
依存する追加:
angular.module('myApp', ['ngMessages']);
このモジュールの基本的な作業は、2つのディレクティブを使用することです。
-
ng-messages
を含むコンテナ -
ng-message
直接ng-message
ng-messages
は、引数として、 ng-messages
がチェックおよびng-messages
されるキーのコレクションを受け取ります。これらのキーは、比較のための引数として文字列または式を受け取ります( 1.4から開始)。
コメントを追加するための同じフォームの例を繰り返しますが、 ngMessages
ます。
<div ng-messages="commentForm.message.$error" class="warnings"> <p ng-message="minlength"> 5 </p> <p ng-message="maxlength"> 500 </p> <p ng-message="required"> </p> </div>
ngMessages
は要素としてngMessages
使用できます:
<ng-messages for="commentForm.message.$error" class="warnings"> <ng-message when="minlength"> 5 </ng> <ng-message when="maxlength"> 500 </ng> <ng-message when="required"> </ng> </ng-messages>
したがって、 ngMessages
はすぐに2つの問題を解決しました。
- リストのような読みやすい
switch
なった条件からの麺 - メッセージを1つずつ表示する問題は、それ自体で解決されました
さらに、メッセージ出力の優先順位付けの問題にも対処しています。 ここではすべてが簡単です。メッセージは、DOM内の場所に従って表示されます。
複数のメッセージ出力を有効にするには、 ng-messages-multiple
属性をng-messages-multiple
ディレクティブに追加しng-messages-multiple
。
<ng-messages ng-messages-multiple for="commentForm.message.$error"> ... </ng-messages>
– ngMessages
, , , :
1.3 ng-messages-include
ng-messages
, ng-messages-include
ng-message
:
1.3
<ng-messages ng-messages-include="length-message" for="commentForm.message.$error"> </ng-messages>
1.4+
<ng-messages for="commentForm.message.$error"> <ng-messages-include="length-message"></ng-messages-include> </ng-messages>
(, ) , :
<script type="script/ng-template" id="length-message"> <ng-message when="minlength"> </ng-message> </script> ... <ng-messages for="commentForm.message.$error"> <ng-messages-include="length-message"></ng-messages-include> </ng-messages> <ng-messages for="anotherForm.someField.$error"> <ng-messages-include="length-message"></ng-messages-include> </ng-messages>
, , . .
, 1.4 , ng-message-exp
:
// error = {type: required, message: ' '}; <ng-messages for="commentForm.message.$error"> <ng-message-exp="error.type"> {{ error.message }} </ng-message-exp> </ng-messages>
ng-message-exp
, ng-message
, ( expression
). , , , AJAX .
– . ng-messages
!
:
-
ng-messages
コントローラー
bindToController
, controller as
, scope
.
, this.something
. .
:
app.directive('someDirective', function () { return { scope: { name: '=' }, controller: function () { this.name = 'Foo' }, controllerAs: 'ctrl' ... }; });
name
.
:
$scope.$watch('name', function (newValue) { this.name = newValue; }.bind(this));
, , , , ?
1.3 :
bindToController
.
app.directive('someDirective', function () { return { scope: { name: '=' }, controller: function () { this.name = 'Foo' }, bindToController: true, ... }; });
ctrl.name
$scope.name
.
1.4 :
app.directive('someDirective', function () { return { scope: true, bindToController: { name: '=' }, controller: function () { this.name = 'Foo' }, ... }; });
scope
bindToController
.
, bindToController
, , , scope
, scope
.
- scope
, true
. bindToController
scope
.
フィルター
{{ expression | filter }}
, , «» ( expression
), ( filter
). , , , .
1.3 : , . , , $digest
, , . , , .
: , , - ? , , «» , ?
このため、静的( stateless
)および動的( stateful
)フィルターの概念は1.3で導入されました。 デフォルトでは、フィルターはstateless
ように動作します。 , $stateful
true
.
例:
angular.module('myApp', []) .filter('customFilter', ['someService', function (someService) { function customFilter(input) { // someService input += someService.getData(); return input; } customFilter.$stateful = true; return customFilter; }]);
重大な変更:
注意深い読者は、そのような変更が実際に古い動的フィルターの動作を壊す可能性があることに気付いているかもしれません。 残念ながら、最初の部分でこの機能を説明するのを忘れていましたが、そのような機会は検討する価値があります。
dateFilter
weeks
おわりに
それだけです - , .
, markdown. - html.
, , :)