シングルページアプリでは、フロントエンド開発者がより優れたソフトウェアエンジニアになることが求められます。 CSSとHTMLはもはや最大の懸念事項ではなく、実際、懸念事項は1つだけではありません。フロントエンド開発者は、XHR、アプリケーションロジック(モデル、ビュー、コントローラー)、パフォーマンス、アニメーション、スタイル、構造、SEO、および外部サービスとの統合を処理する必要があります。これらすべてを組み合わせることで得られる結果は、常に優先されるべきユーザーエクスペリエンス(UX)です。
AngularJSは非常に強力なフレームワークです。これは、GitHubで3番目にスターが付けられたリポジトリです。使い始めるのは難しいことではありませんが、需要の理解を達成することを目的とした目標です。 AngularJS開発者は、ナビゲーション時にリセットされなくなるため、メモリ消費を無視できなくなりました。これはの先駆者です ウェブ開発 。抱きしめよう!
本番環境で推奨される最適化の調整がいくつかあります。それらの1つは、デバッグ情報を無効にすることです。
DebugInfoEnabled
はデフォルトでtrueに設定され、DOMノードを介したスコープアクセスを許可します。 JavaScriptコンソールからそれを試したい場合は、DOM要素を選択し、次のコマンドでそのスコープにアクセスします。
angular.element(document.body).scope()
使わなくても重宝します jQuery CSSを使用しますが、コンソールの外部では使用しないでください。その理由は$compileProvider.debugInfoEnabled
のときですfalseに設定され、.scope()
を呼び出しますDOMノードではundefined
を返します。
これは、本番環境で推奨される数少ないオプションの1つです。
本番環境でも、コンソールからスコープにアクセスできることに注意してください。電話angular.reloadWithDebugInfo()
コンソールから、アプリはまさにそれを行います。
もしそうなら、あなたはおそらくそれを読んだでしょう ngモデルにドットがない 、あなたはそれを間違っていました。継承に関しては、そのステートメントはしばしば真実です。スコープには、JavaScriptに典型的な継承のプロトタイプモデルがあり、ネストされたスコープはAngularJSに共通です。多くのディレクティブは、ngRepeat
、ngIf
、ngController
などの子スコープを作成します。モデルを解決するとき、ルックアップは現在のスコープで開始され、$rootScope
までのすべての親スコープを通過します。
ただし、新しい値を設定する場合、何が起こるかは、変更するモデル(変数)の種類によって異なります。モデルがプリミティブの場合、子スコープは新しいモデルを作成するだけです。ただし、変更がモデルオブジェクトのプロパティに対するものである場合、親スコープを検索すると、参照されているオブジェクトが検索され、その実際のプロパティが変更されます。新しいモデルは現在のスコープに設定されないため、マスキングは発生しません。
function MainController($scope) { $scope.foo = 1; $scope.bar = {innerProperty: 2}; } angular.module('myApp', []) .controller('MainController', MainController);
OUTER SCOPE:
{{ foo }}
{{ bar.innerProperty }}
INNER SCOPE
{{ foo }}
{{ bar.innerProperty }}
Set primitive Mutate object
「プリミティブの設定」というラベルの付いたボタンをクリックすると、内側のスコープのfooが2に設定されますが、外側のスコープのfooは変更されません。
「オブジェクトの変更」というラベルの付いたボタンをクリックすると、親スコープからバーのプロパティが変更されます。内側のスコープには変数がないため、シャドウイングは発生せず、barの表示値は両方のスコープで3になります。
これを行う別の方法は、親スコープとルートスコープがすべてのスコープから参照されるという事実を活用することです。 $parent
および$root
オブジェクトを使用して、ビューから直接親スコープと$rootScope
にそれぞれアクセスできます。これは強力な方法かもしれませんが、ストリームの特定のスコープをターゲットにすることに問題があるため、私はそれが好きではありません。スコープに固有のプロパティを設定してアクセスする別の方法があります-controllerAs
を使用します構文。
注入された$ scopeの代わりにコントローラーオブジェクトを使用するようにモデルを割り当てるための代替の最も効率的な方法。スコープを挿入する代わりに、次のようなモデルを定義できます。
function MainController($scope) { this.foo = 1; var that = this; var setBar = function () { // that.bar = {someProperty: 2}; this.bar = {someProperty: 2}; }; setBar.call(this); // there are other conventions: // var MC = this; // setBar.call(this); when using 'this' inside setBar() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
これははるかに混乱が少ないです。特に、ネストされた状態の場合のように、ネストされたスコープが多数ある場合。
controllerAs構文にはさらに多くのものがあります。
コントローラオブジェクトがどのように公開されるかについては、いくつかの注意点があります。これは基本的に、通常のモデルと同様に、コントローラーのスコープに設定されたオブジェクトです。
コントローラオブジェクトのプロパティを監視する必要がある場合は、関数を監視できますが、必須ではありません。次に例を示します。
function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }
実行する方が簡単です。
function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }
つまり、スコープチェーンの下流でも、子コントローラーからMCにアクセスできます。
function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }
ただし、これを実行するには、controllerAに使用する頭字語と一致している必要があります。それを設定する方法は少なくとも3つあります。あなたはすでに最初のものを見ました:
…
ただし、ui-router
を使用する場合、そのようにコントローラーを指定するとエラーが発生しやすくなります。状態の場合、コントローラーは状態構成で指定する必要があります。
angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });
注釈を付ける別の方法があります。
(…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })
ディレクティブでも同じことができます。
function AnotherController() { this.text = 'abc'; } function testForApeeScape() { return { controller: 'AnotherController as AC', template: '{{ AC.text }}
' }; } angular.module('myApp', []) .controller('AnotherController', AnotherController) .directive('testForApeeScape', testForApeeScape);
あまり簡潔ではありませんが、注釈を付ける他の方法も有効です。
function testForApeeScape() { return { controller: 'AnotherController', controllerAs: 'AC', template: '{{ AC.text }}
' }; }
AngularJSの事実上のルーティングソリューションは、これまでui-router
でした。しばらく前にコアから削除されたngRouteモジュールは、より高度なルーティングには基本的すぎました。
新しいNgRouter
があります途中ですが、作者はまだ制作するには時期尚早だと考えています。これを書いているとき、安定したAngularは1.3.15で、ui-router
岩。
主な理由:
ここでは、AngularJSエラーを回避するための状態のネストについて説明します。
これは、複雑でありながら標準的なユースケースと考えてください。ホームページビューと商品ビューのアプリがあります。製品ビューには、イントロ、ウィジェット、コンテンツの3つのセクションがあります。状態を切り替えるときにウィジェットを永続化し、リロードしないようにします。ただし、コンテンツはリロードする必要があります。
次のHTML製品インデックスページ構造を検討してください。
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
これはHTMLコーダーから取得できるものであり、ファイルと状態に分割する必要があります。私は通常、必要に応じてグローバルデータを保持する抽象的なMAIN状態があるという規則に従います。 $ rootScopeの代わりにそれを使用してください。ザ・ メイン 状態は、すべてのページで必要な静的HTMLも保持します。 index.htmlをクリーンに保ちます。
function config($stateProvider) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { abstract: true, url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html' }) // A SIMPLE HOMEPAGE .state('main.homepage', { url: '', controller: 'HomepageController as HC', templateUrl: '/routing-demo/homepage.html' }) // THE ABOVE IS ALL GOOD, HERE IS TROUBLE // A COMPLEX PRODUCT PAGE .state('main.product', { abstract: true, url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }); } angular.module('articleApp', [ 'ui.router' ]) .config(config);
main.product.index
次に、製品インデックスページを見てみましょう。
main.product.details
ご覧のとおり、製品インデックスページには3つの名前付きビューがあります。 1つはイントロ用、もう1つはウィジェット用、もう1つは製品用です。スペックを満たしています!それでは、ルーティングを設定しましょう。
ui-router
それが最初のアプローチです。さて、// A COMPLEX PRODUCT PAGE // WITH NO MORE TROUBLE .state('main.product', { abstract: true, url: ':id', views: { // TARGETING THE UNNAMED VIEW IN MAIN.HTML '@main': { controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html' }, // TARGETING THE WIDGET VIEW IN PRODUCT.HTML // BY DEFINING A CHILD VIEW ALREADY HERE, WE ENSURE IT DOES NOT RELOAD ON CHILD STATE CHANGE ' [email protected] ': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' } } }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } });
を切り替えるとどうなりますかおよび$urlRouterProvider.deferIntercept()
?コンテンツとウィジェットは再読み込みされますが、コンテンツを再読み込みするだけです。これには問題があり、開発者は実際にその機能だけをサポートするルーターを作成しました。この名前の1つは スティッキービュー 。幸い、'use strict'; function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // THE BELOW CALL CANNOT BE SPIED ON WITH JASMINE publicMethod1('someArgument'); }; // IF THE LITERAL IS RETURNED THIS WAY, IT CAN'T BE REFERRED TO FROM INSIDE return { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; } angular.module('app', []) .factory('yoda', yoda);
箱から出してそれをサポートします 絶対名付きビューターゲティング 。
publicMethod1
状態定義を親ビューに移動することで、これも抽象的であり、通常はその子の兄弟に影響を与えるURLを切り替えるときに、子ビューが再読み込みされないようにすることができます。もちろん、ウィジェットは単純なディレクティブでもかまいません。ただし、重要なのは、別の複雑なネストされた状態である可能性もあるということです。
function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // the below call cannot be spied on publicMethod1('someArgument'); // BUT THIS ONE CAN! hostObject.publicMethod1('aBetterArgument'); }; var hostObject = { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; return hostObject; }
を使用してこれを行う別の方法がありますが、実際には状態構成を使用する方が良いと思います。ルートの傍受に興味がある場合は、以下の小さなチュートリアルを作成しました。 スタックオーバーフロー 。
この 間違い はより軽い口径であり、AngularJSエラーメッセージを回避するよりもスタイルの問題です。以前、匿名関数をAngularInternalの宣言に渡すことはめったにないことに気付いたかもしれません。私は通常、最初に関数を定義してから、それを渡します。
これは単なる機能以上のものです。このアプローチは、スタイルガイド、特にAirbnbとToddMottoを読んで得たものです。いくつかの利点があり、欠点はほとんどないと思います。
まず、関数とオブジェクトが変数に割り当てられていると、それらをはるかに簡単に操作および変更できます。第二に、コードはよりクリーンで、ファイルに簡単に分割できます。それは保守性を意味します。グローバル名前空間を汚染したくない場合は、すべてのファイルをIIFEでラップします。 3番目の理由はテスト容易性です。この例を考えてみましょう。
function scoringService($q) { var scoreItems = function (items, weights) { var deferred = $q.defer(); var worker = new Worker('/worker-demo/scoring.worker.js'); var orders = { items: items, weights: weights }; worker.postMessage(orders); worker.onmessage = function (e) { if (e.data && e.data.ready) { deferred.resolve(e.data.items); } }; return deferred.promise; }; var hostObject = { scoreItems: function (items, weights) { return scoreItems(items, weights); } }; return hostObject; } angular.module('app.worker') .factory('scoringService', scoringService);
これで'use strict'; function scoringFunction(items, weights) { var itemsArray = []; for (var i = 0; i b.sum) { return -1; } else if (a.sum
scoringService.scoreItems()
事実上、コードはより再利用可能で慣用的であるため、これはスタイルだけではありません。開発者はより表現力を得る。すべてのコードを自己完結型のブロックに分割すると、簡単になります。
シナリオによっては、一連のフィルター、デコレーター、そして最後にソートアルゴリズムを通過させることにより、複雑なオブジェクトの大規模な配列を処理する必要がある場合があります。ユースケースの1つは、アプリをオフラインで動作させる必要がある場合、またはデータ表示のパフォーマンスが重要な場合です。また、JavaScriptはシングルスレッドであるため、ブラウザーをフリーズするのは比較的簡単です。
また、Webワーカーでは簡単に回避できます。特にAngularJS用にそれを処理する人気のあるライブラリはないようです。ただし、実装は簡単なので、最善の方法かもしれません。
まず、サービスを設定しましょう。
.toString()
さて、労働者:
function resolve(index, timeout) { return { data: function($q, $timeout) { var deferred = $q.defer(); $timeout(function () { deferred.resolve(console.log('Data resolve called ' + index)); }, timeout); return deferred.promise; } }; } function configResolves($stateProvide) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html', resolve: resolve(1, 1597) }) // A COMPLEX PRODUCT PAGE .state('main.product', { url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', resolve: resolve(2, 2584) }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } }, resolve: resolve(3, 987) }); }
ここで、通常どおりサービスを挿入し、Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executed
を処理します。 promiseを返す他のサービスメソッドと同じように。重い処理は別のスレッドで実行され、UXに害はありません。
注意すべき点:
.run()
渡されたプロパティで、それは正しく機能しました。解決すると、ビューの読み込みに時間がかかります。フロントエンドアプリの高性能が私たちの主な目標だと思います。アプリがAPIからのデータを待機している間に、ビューの一部をレンダリングすることは問題ではありません。
この設定を検討してください。
this.maxPrice = '100'; this.price = '55'; $scope.$watch('MC.price', function (newVal) { if (newVal || newVal === 0) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } });
コンソール出力は次のようになります。
this.maxPrice = '100'; this.price = '55'; this.priceTemporary = '55'; $scope.$watch('MC.price', function (newVal) { if (!isNaN(newVal)) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } }); var timeoutInstance; $scope.$watch('MC.priceTemporary', function (newVal) { if (!isNaN(newVal)) { if (timeoutInstance) { $timeout.cancel(timeoutInstance); } timeoutInstance = $timeout(function () { $scope.MC.price = newVal; }, 144); } });
これは基本的に次のことを意味します。
これは、ユーザーが出力を見る前に、すべての依存関係を待つ必要があることを意味します。確かに、そのデータが必要です。ビューの前に絶対に必要な場合は、$digest()
に入れてくださいブロック。それ以外の場合は、コントローラーからサービスを呼び出して、ハーフロード状態を適切に処理します。進行中の作業を確認すること(およびコントローラーはすでに実行されているため、実際には進行中です)は、アプリを停止させるよりも優れています。
a)モデルへのスライダーの取り付けなど、ダイジェストループが多すぎる原因
これはAngularJSエラーを引き起こす可能性のある一般的な問題ですが、スライダーの例で説明します。拡張機能が必要だったので、このスライダーライブラリである角度範囲スライダーを使用していました。そのディレクティブには、最小バージョンで次の構文があります。
ng-click
コントローラ内の次のコードを検討してください。
input
そのため、動作が遅くなります。カジュアルな解決策は、入力にタイムアウトを設定することです。ただし、これは必ずしも便利なわけではなく、すべての場合に実際のモデル変更を遅らせたくない場合もあります。
したがって、タイムアウト時に作業モデルを変更するためにバインドされた一時モデルを追加します。
$timeout
およびコントローラー内:
$http
b)$ applyAsyncを使用しない
AngularJSには、$watch
を呼び出すためのポーリングメカニズムがありません。ディレクティブ(例:.$applyAsync()
、$digest()
)、サービス(applyAsync
、$http
)、および評価するメソッド(mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
)を使用するためにのみ実行されます。コードを作成し、後でダイジェストを呼び出します。
何.click()
次の$apply()
まで式の解決を遅らせますかサイクル。タイムアウトが0の後にトリガーされます。これは、実際には約10ミリ秒です。
$scope.$root.$digest()
を使用するには2つの方法があります今。 $rootScope.$digest()
の自動化された方法リクエスト、および残りの手動の方法。
ほぼ同時に返されるすべてのhttpリクエストを1つのダイジェストで解決するには、次のようにします。
$scope.$digest()
手動の方法は、実際にどのように機能するかを示しています。バニラJSイベントリスナーまたはjQuery I AM DIRECTIVE$scope.$applyAsync()
、またはその他の外部ライブラリへのコールバックで実行される関数について考えてみます。実行してモデルを変更した後、まだ$digest()
でラップしていない場合 remove directive
に電話する必要があります(function MainController($rootScope, $scope) { this.removeDirective = function () { $rootScope.$emit('destroyDirective'); }; } function testForApeeScape($rootScope, $timeout) { return { link: function (scope, element, attributes) { var destroyListener = $rootScope.$on('destroyDirective', function () { scope.$destroy(); }); // adding a timeout for the DOM to get ready $timeout(function () { scope.toBeDetached = element.find('p'); }); scope.$on('$destroy', function () { destroyListener(); element.remove(); }); }, template: '
)、または少なくともscope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });
。それ以外の場合、変更は表示されません。
1つのフローでこれを複数回実行すると、実行速度が低下する可能性があります。 … the isolated scope is not available here, look: {{ isolatedModel }}
に電話することを検討してください代わりに式に。それらすべてに対して1つのダイジェストサイクルのみを呼び出すように設定されます。
c)画像の大量処理を行う
パフォーマンスが低下した場合は、Chromeデベロッパーツールのタイムラインを使用して理由を調査できます。このツールについては、間違い#17で詳しく説明します。記録後にタイムライングラフが緑色で占められている場合、パフォーマンスの問題は画像の処理に関連している可能性があります。これは厳密にはAngularJSとは関係ありませんが、AngularJSのパフォーマンスの問題(グラフではほとんど黄色)に加えて発生する可能性があります。フロントエンドエンジニアとして、私たちは完全なエンドプロジェクトについて考える必要があります。
評価する時間を取ってください:
上記の少なくとも3つに「はい」と答えた場合は、緩和することを検討してください。おそらく、さまざまな画像サイズを提供でき、サイズ変更はまったくできません。たぶん、「transform:translateZ(0)」強制GPU処理ハックを追加することができます。または、ハンドラーにrequestAnimationFrameを使用します。
AngularJSでjQueryを使用することは推奨されておらず、避けるべきだとよく耳にします。これらのステートメントの背後にある理由を理解することが不可欠です。私が見る限り、少なくとも3つの理由がありますが、実際のブロッカーではありません。
理由1: jQueryコードを実行するときは、function MainController($interval) { this.foo = { bar: 1 }; this.baz = 1; var that = this; $interval(function () { that.foo.bar++; }, 144); $interval(function () { that.baz++; }, 144); this.quux = [1,2,3]; }
を呼び出す必要があります。あなた自身。多くの場合、 AngularJSソリューション これはAngularJS用に調整されており、jQueryよりもAngular内でより適切に使用できます(例:ng-clickまたはイベントシステム)。
理由2: アプリの構築についての考え方。ナビゲート時にリロードするJavaScriptをWebサイトに追加している場合は、メモリ消費についてあまり心配する必要はありません。シングルページアプリでは、心配する必要はありません。クリーンアップしないと、アプリに数分以上費やすユーザーでパフォーマンスの問題が増大する可能性があります。
理由3: クリーンアップは、実際には実行および分析するのが最も簡単なことではありません。 (ブラウザーで)スクリプトからガベージコレクターを呼び出す方法はありません。最終的には、DOMツリーが切り離される可能性があります。私は例を作成しました(jQueryはindex.htmlにロードされます):
function testDirective() { var postLink = function (scope, element, attrs) { scope.$watch(attrs.watchAttribute, function (newVal) { if (newVal) { // take a look in the console // we can't use the attribute directly console.log(attrs.watchAttribute); // the newVal is evaluated, and it can be used scope.modifiedFooBar = newVal.bar * 10; } }, true); attrs.$observe('observeAttribute', function (newVal) { scope.observed = newVal; }); }; return { link: postLink, templateUrl: '/attributes-demo/test-directive.html' }; }
これは、テキストを出力する単純なディレクティブです。その下にボタンがあり、ディレクティブを手動で破棄するだけです。
したがって、ディレクティブが削除されても、scope.toBeDetachedにはDOMツリーへの参照が残ります。 Chrome開発ツールでは、[プロファイル]タブにアクセスしてから[ヒープスナップショットを取得]タブにアクセスすると、出力に次のように表示されます。
あなたは少数で生きることができます、しかしあなたがトンを持っているならばそれは悪いです。特に、例のように何らかの理由でスコープに保存する場合は特にそうです。 DOM全体がすべてのダイジェストで評価されます。問題のある分離されたDOMツリーは、4つのノードを持つものです。では、これをどのように解決できますか?
attrs.watchAttribute
4つのエントリを持つデタッチされたDOMツリーが削除されます!
この例では、ディレクティブは同じスコープを使用し、スコープにDOM要素を格納します。そのようにそれを示すことは私にとってより簡単でした。変数に格納できるため、必ずしもそれほど悪くなるとは限りません。ただし、その変数または同じ関数スコープの他の変数を参照していたクロージャが存在する場合は、メモリを消費します。
単一の場所で使用されることがわかっているディレクティブが必要な場合、または使用されている環境と競合することが予想されない場合は、分離スコープを使用する必要はありません。最近、再利用可能なコンポーネントを作成する傾向がありますが、コア角度ディレクティブが分離スコープをまったく使用していないことをご存知ですか?
主な理由は2つあります。要素に2つの分離されたスコープディレクティブを適用できないことと、ネスト/継承/イベント処理で問題が発生する可能性があることです。特にトランスクルージョンに関して-効果はあなたが期待するものではないかもしれません。
したがって、これは失敗します。
scope.$watch()
また、ディレクティブを1つだけ使用した場合でも、分離されたスコープモデルも、isolatedScopeDirectiveでブロードキャストされたイベントもAnotherControllerで使用できないことに気付くでしょう。悲しいことに、トランスクルージョンマジックを曲げて使用して機能させることができますが、ほとんどのユースケースでは、分離する必要はありません。
MC.foo
だから、今2つの質問:
2つの方法があり、どちらの方法でも、属性に値を渡します。このMainControllerについて考えてみましょう。
$watch()
これはこのビューを制御します。
MC.foo
「watch-attribute」は補間されていないことに注意してください。 JSマジックにより、すべて機能します。ディレクティブの定義は次のとおりです。
$parse
$eval
に注意してください$destroy
に渡されます引用符なし!つまり、実際に$ watchに渡されたのは、文字列function cleanMeUp($interval, $rootScope, $timeout) { var postLink = function (scope, element, attrs) { var rootModelListener = $rootScope.$watch('someModel', function () { // do something }); var myInterval = $interval(function () { // do something in intervals }, 2584); var myTimeout = $timeout(function () { // defer some action here }, 1597); scope.domElement = element; $timeout(function () { // calling $destroy manually for testing purposes scope.$destroy(); }, 987); // here is where the cleanup happens scope.$on('$destroy', function () { // disable the listener rootModelListener(); // cancel the interval and timeout $interval.cancel(myInterval); $timeout.cancel(myTimeout); // nullify the DOM-bound model scope.domElement = null; }); element.on('$destroy', function () { // this is a jQuery event // clean up all vanilla JavaScript / jQuery artifacts here // respectful jQuery plugins have $destroy handlers, // that is the reason why this event is emitted... // follow the standards. }); };
でした。ただし、$destroy
に渡される文字列があるため、機能します。スコープに対して評価され、$digest()
スコープで利用可能です。これは、AngularJSコアディレクティブで属性を監視する最も一般的な方法でもあります。
テンプレートについてはgithubのコードを参照し、{{ model }}
を調べてください。および
さらに素晴らしいために。
AngularJSはあなたに代わっていくつかの作業を行いますが、すべてではありません。以下は手動でクリーンアップする必要があります。
vastArray
イベントこれを手動で行わないと、予期しない動作やメモリリークが発生します。さらに悪いことに、これらはすぐには表示されませんが、最終的には忍び寄ります。マーフィーの法則。
驚くべきことに、AngularJSはこれらすべてに対処するための便利な方法を提供します。
item.velocity
jQueryに注意してください{{ someModel }}
イベント。 AngularJSと同じように呼ばれますが、個別に処理されます。スコープ$ watchersはjQueryイベントに反応しません。
これは今では非常に簡単なはずです。ここで理解すべきことが1つあります:ng-if
。バインディングng-repeat
ごとに、AngularJSはウォッチャーを作成します。すべての消化段階で、そのような各結合が評価され、前の値と比較されます。これはダーティチェックと呼ばれ、$ digestが行うことです。最後のチェック以降に値が変更された場合、ウォッチャーコールバックが発生します。そのウォッチャーコールバックがモデル($ scope変数)を変更する場合、例外がスローされると、新しい$ digestサイクルが発生します(最大10)。
式が複雑でない限り、ブラウザには何千ものバインディングがあっても問題はありません。 「何人のウォッチャーがいても大丈夫」の一般的な答えは2000です。
では、どうすればウォッチャーの数を制限できますか?スコープモデルが変更されるとは思わないときに、スコープモデルを監視しないこと。ワンタイムバインディングがコアになっているため、AngularJS1.3以降はかなり簡単です。
$watch()
後$Watchers
および$watch()
一度評価されると、二度と変更されることはありません。配列にフィルターを適用することはできますが、それらは問題なく機能します。配列自体が評価されないだけです。多くの場合、それは勝利です。
このAngularJSエラーは、間違い9.bと13ですでに部分的にカバーされています。これはより完全な説明です。 AngularJSは、ウォッチャーへのコールバック関数の結果としてDOMを更新します。すべてのバインディング、つまりディレクティブ$rootScope.$digest()
ウォッチャーを設定しますが、ウォッチャーは$scope
のような他の多くのディレクティブにも設定されますおよび$watch()
。ソースコードを見てください、それは非常に読みやすいです。ウォッチャーは手動で設定することもできますが、おそらく自分で少なくとも数回は設定したことがあるでしょう。
$digest()
ersはスコープにバインドされています。 .$apply
.$applyAsync
のスコープに対して評価される文字列を受け取ることができますにバインドされていました。また、機能を評価することもできます。また、コールバックも受けます。したがって、.$evalAsync
の場合が呼び出されると、登録されているすべてのモデル(つまり、$digest()
変数)が評価され、以前の値と比較されます。値が一致しない場合、$digest()
へのコールバック実行されます。
モデルの値が変更された場合でも、コールバックは次のダイジェストフェーズまで発生しないことを理解することが重要です。これは、いくつかのダイジェストサイクルで構成されている可能性があるため、「フェーズ」と呼ばれます。ウォッチャーのみがスコープモデルを変更した場合、別のダイジェストサイクルが実行されます。
だが describe('some module', function () { it('should call the name-it service…', function () { // leave this empty for now }); ... });
ポーリングされません 。コアディレクティブ、サービス、メソッドなどから呼び出されます。'use strict'; var gulp = require('gulp'); var args = require('yargs').argv; var browserSync = require('browser-sync'); var karma = require('gulp-karma'); var protractor = require('gulp-protractor').protractor; var webdriverUpdate = require('gulp-protractor').webdriver_update; function test() { // Be sure to return the stream // NOTE: Using the fake './foobar' so as to run the files // listed in karma.conf.js INSTEAD of what was passed to // gulp.src ! return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'run' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); this.emit('end'); //instead of erroring the stream, end it }); } function tdd() { return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'start' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); // this.emit('end'); // not ending the stream here }); } function runProtractor () { var argument = args.suite || 'all'; // NOTE: Using the fake './foobar' so as to run the files // listed in protractor.conf.js, instead of what was passed to // gulp.src return gulp.src('./foobar') .pipe(protractor({ configFile: 'test/protractor.conf.js', args: ['--suite', argument] })) .on('error', function (err) { // Make sure failed tests cause gulp to exit non-zero throw err; }) .on('end', function () { // Close browser sync server browserSync.exit(); }); } gulp.task('tdd', tdd); gulp.task('test', test); gulp.task('test-e2e', ['webdriver-update'], runProtractor); gulp.task('webdriver-update', webdriverUpdate);
、console.log()
、debugInfo
、または最終的に呼び出すものを呼び出さないカスタム関数からモデルを変更した場合$(document.body).scope().$root
、バインディングは更新されません。
ちなみに、$(
AngularJS開発者が犯す最も一般的な18の間違い
シングルページアプリでは、フロントエンド開発者がより優れたソフトウェアエンジニアになることが求められます。 CSSとHTMLはもはや最大の懸念事項ではなく、実際、懸念事項は1つだけではありません。フロントエンド開発者は、XHR、アプリケーションロジック(モデル、ビュー、コントローラー)、パフォーマンス、アニメーション、スタイル、構造、SEO、および外部サービスとの統合を処理する必要があります。これらすべてを組み合わせることで得られる結果は、常に優先されるべきユーザーエクスペリエンス(UX)です。
AngularJSは非常に強力なフレームワークです。これは、GitHubで3番目にスターが付けられたリポジトリです。使い始めるのは難しいことではありませんが、需要の理解を達成することを目的とした目標です。 AngularJS開発者は、ナビゲーション時にリセットされなくなるため、メモリ消費を無視できなくなりました。これはの先駆者です ウェブ開発 。抱きしめよう!
本番環境で推奨される最適化の調整がいくつかあります。それらの1つは、デバッグ情報を無効にすることです。
DebugInfoEnabled
はデフォルトでtrueに設定され、DOMノードを介したスコープアクセスを許可します。 JavaScriptコンソールからそれを試したい場合は、DOM要素を選択し、次のコマンドでそのスコープにアクセスします。
angular.element(document.body).scope()
使わなくても重宝します jQuery CSSを使用しますが、コンソールの外部では使用しないでください。その理由は$compileProvider.debugInfoEnabled
のときですfalseに設定され、.scope()
を呼び出しますDOMノードではundefined
を返します。
これは、本番環境で推奨される数少ないオプションの1つです。
本番環境でも、コンソールからスコープにアクセスできることに注意してください。電話angular.reloadWithDebugInfo()
コンソールから、アプリはまさにそれを行います。
もしそうなら、あなたはおそらくそれを読んだでしょう ngモデルにドットがない 、あなたはそれを間違っていました。継承に関しては、そのステートメントはしばしば真実です。スコープには、JavaScriptに典型的な継承のプロトタイプモデルがあり、ネストされたスコープはAngularJSに共通です。多くのディレクティブは、ngRepeat
、ngIf
、ngController
などの子スコープを作成します。モデルを解決するとき、ルックアップは現在のスコープで開始され、$rootScope
までのすべての親スコープを通過します。
ただし、新しい値を設定する場合、何が起こるかは、変更するモデル(変数)の種類によって異なります。モデルがプリミティブの場合、子スコープは新しいモデルを作成するだけです。ただし、変更がモデルオブジェクトのプロパティに対するものである場合、親スコープを検索すると、参照されているオブジェクトが検索され、その実際のプロパティが変更されます。新しいモデルは現在のスコープに設定されないため、マスキングは発生しません。
function MainController($scope) { $scope.foo = 1; $scope.bar = {innerProperty: 2}; } angular.module('myApp', []) .controller('MainController', MainController);
OUTER SCOPE:
{{ foo }}
{{ bar.innerProperty }}
INNER SCOPE
{{ foo }}
{{ bar.innerProperty }}
Set primitive Mutate object
「プリミティブの設定」というラベルの付いたボタンをクリックすると、内側のスコープのfooが2に設定されますが、外側のスコープのfooは変更されません。
「オブジェクトの変更」というラベルの付いたボタンをクリックすると、親スコープからバーのプロパティが変更されます。内側のスコープには変数がないため、シャドウイングは発生せず、barの表示値は両方のスコープで3になります。
これを行う別の方法は、親スコープとルートスコープがすべてのスコープから参照されるという事実を活用することです。 $parent
および$root
オブジェクトを使用して、ビューから直接親スコープと$rootScope
にそれぞれアクセスできます。これは強力な方法かもしれませんが、ストリームの特定のスコープをターゲットにすることに問題があるため、私はそれが好きではありません。スコープに固有のプロパティを設定してアクセスする別の方法があります-controllerAs
を使用します構文。
注入された$ scopeの代わりにコントローラーオブジェクトを使用するようにモデルを割り当てるための代替の最も効率的な方法。スコープを挿入する代わりに、次のようなモデルを定義できます。
function MainController($scope) { this.foo = 1; var that = this; var setBar = function () { // that.bar = {someProperty: 2}; this.bar = {someProperty: 2}; }; setBar.call(this); // there are other conventions: // var MC = this; // setBar.call(this); when using 'this' inside setBar() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
これははるかに混乱が少ないです。特に、ネストされた状態の場合のように、ネストされたスコープが多数ある場合。
controllerAs構文にはさらに多くのものがあります。
コントローラオブジェクトがどのように公開されるかについては、いくつかの注意点があります。これは基本的に、通常のモデルと同様に、コントローラーのスコープに設定されたオブジェクトです。
コントローラオブジェクトのプロパティを監視する必要がある場合は、関数を監視できますが、必須ではありません。次に例を示します。
function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }
実行する方が簡単です。
function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }
つまり、スコープチェーンの下流でも、子コントローラーからMCにアクセスできます。
function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }
ただし、これを実行するには、controllerAに使用する頭字語と一致している必要があります。それを設定する方法は少なくとも3つあります。あなたはすでに最初のものを見ました:
…
ただし、ui-router
を使用する場合、そのようにコントローラーを指定するとエラーが発生しやすくなります。状態の場合、コントローラーは状態構成で指定する必要があります。
angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });
注釈を付ける別の方法があります。
(…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })
ディレクティブでも同じことができます。
function AnotherController() { this.text = 'abc'; } function testForApeeScape() { return { controller: 'AnotherController as AC', template: '{{ AC.text }}
' }; } angular.module('myApp', []) .controller('AnotherController', AnotherController) .directive('testForApeeScape', testForApeeScape);
あまり簡潔ではありませんが、注釈を付ける他の方法も有効です。
function testForApeeScape() { return { controller: 'AnotherController', controllerAs: 'AC', template: '{{ AC.text }}
' }; }
AngularJSの事実上のルーティングソリューションは、これまでui-router
でした。しばらく前にコアから削除されたngRouteモジュールは、より高度なルーティングには基本的すぎました。
新しいNgRouter
があります途中ですが、作者はまだ制作するには時期尚早だと考えています。これを書いているとき、安定したAngularは1.3.15で、ui-router
岩。
主な理由:
ここでは、AngularJSエラーを回避するための状態のネストについて説明します。
これは、複雑でありながら標準的なユースケースと考えてください。ホームページビューと商品ビューのアプリがあります。製品ビューには、イントロ、ウィジェット、コンテンツの3つのセクションがあります。状態を切り替えるときにウィジェットを永続化し、リロードしないようにします。ただし、コンテンツはリロードする必要があります。
次のHTML製品インデックスページ構造を検討してください。
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
これはHTMLコーダーから取得できるものであり、ファイルと状態に分割する必要があります。私は通常、必要に応じてグローバルデータを保持する抽象的なMAIN状態があるという規則に従います。 $ rootScopeの代わりにそれを使用してください。ザ・ メイン 状態は、すべてのページで必要な静的HTMLも保持します。 index.htmlをクリーンに保ちます。
function config($stateProvider) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { abstract: true, url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html' }) // A SIMPLE HOMEPAGE .state('main.homepage', { url: '', controller: 'HomepageController as HC', templateUrl: '/routing-demo/homepage.html' }) // THE ABOVE IS ALL GOOD, HERE IS TROUBLE // A COMPLEX PRODUCT PAGE .state('main.product', { abstract: true, url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }); } angular.module('articleApp', [ 'ui.router' ]) .config(config);
main.product.index
次に、製品インデックスページを見てみましょう。
main.product.details
ご覧のとおり、製品インデックスページには3つの名前付きビューがあります。 1つはイントロ用、もう1つはウィジェット用、もう1つは製品用です。スペックを満たしています!それでは、ルーティングを設定しましょう。
ui-router
それが最初のアプローチです。さて、// A COMPLEX PRODUCT PAGE // WITH NO MORE TROUBLE .state('main.product', { abstract: true, url: ':id', views: { // TARGETING THE UNNAMED VIEW IN MAIN.HTML '@main': { controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html' }, // TARGETING THE WIDGET VIEW IN PRODUCT.HTML // BY DEFINING A CHILD VIEW ALREADY HERE, WE ENSURE IT DOES NOT RELOAD ON CHILD STATE CHANGE ' [email protected] ': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' } } }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } });
を切り替えるとどうなりますかおよび$urlRouterProvider.deferIntercept()
?コンテンツとウィジェットは再読み込みされますが、コンテンツを再読み込みするだけです。これには問題があり、開発者は実際にその機能だけをサポートするルーターを作成しました。この名前の1つは スティッキービュー 。幸い、'use strict'; function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // THE BELOW CALL CANNOT BE SPIED ON WITH JASMINE publicMethod1('someArgument'); }; // IF THE LITERAL IS RETURNED THIS WAY, IT CAN'T BE REFERRED TO FROM INSIDE return { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; } angular.module('app', []) .factory('yoda', yoda);
箱から出してそれをサポートします 絶対名付きビューターゲティング 。
publicMethod1
状態定義を親ビューに移動することで、これも抽象的であり、通常はその子の兄弟に影響を与えるURLを切り替えるときに、子ビューが再読み込みされないようにすることができます。もちろん、ウィジェットは単純なディレクティブでもかまいません。ただし、重要なのは、別の複雑なネストされた状態である可能性もあるということです。
function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // the below call cannot be spied on publicMethod1('someArgument'); // BUT THIS ONE CAN! hostObject.publicMethod1('aBetterArgument'); }; var hostObject = { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; return hostObject; }
を使用してこれを行う別の方法がありますが、実際には状態構成を使用する方が良いと思います。ルートの傍受に興味がある場合は、以下の小さなチュートリアルを作成しました。 スタックオーバーフロー 。
この 間違い はより軽い口径であり、AngularJSエラーメッセージを回避するよりもスタイルの問題です。以前、匿名関数をAngularInternalの宣言に渡すことはめったにないことに気付いたかもしれません。私は通常、最初に関数を定義してから、それを渡します。
これは単なる機能以上のものです。このアプローチは、スタイルガイド、特にAirbnbとToddMottoを読んで得たものです。いくつかの利点があり、欠点はほとんどないと思います。
まず、関数とオブジェクトが変数に割り当てられていると、それらをはるかに簡単に操作および変更できます。第二に、コードはよりクリーンで、ファイルに簡単に分割できます。それは保守性を意味します。グローバル名前空間を汚染したくない場合は、すべてのファイルをIIFEでラップします。 3番目の理由はテスト容易性です。この例を考えてみましょう。
function scoringService($q) { var scoreItems = function (items, weights) { var deferred = $q.defer(); var worker = new Worker('/worker-demo/scoring.worker.js'); var orders = { items: items, weights: weights }; worker.postMessage(orders); worker.onmessage = function (e) { if (e.data && e.data.ready) { deferred.resolve(e.data.items); } }; return deferred.promise; }; var hostObject = { scoreItems: function (items, weights) { return scoreItems(items, weights); } }; return hostObject; } angular.module('app.worker') .factory('scoringService', scoringService);
これで'use strict'; function scoringFunction(items, weights) { var itemsArray = []; for (var i = 0; i b.sum) { return -1; } else if (a.sum
scoringService.scoreItems()
事実上、コードはより再利用可能で慣用的であるため、これはスタイルだけではありません。開発者はより表現力を得る。すべてのコードを自己完結型のブロックに分割すると、簡単になります。
シナリオによっては、一連のフィルター、デコレーター、そして最後にソートアルゴリズムを通過させることにより、複雑なオブジェクトの大規模な配列を処理する必要がある場合があります。ユースケースの1つは、アプリをオフラインで動作させる必要がある場合、またはデータ表示のパフォーマンスが重要な場合です。また、JavaScriptはシングルスレッドであるため、ブラウザーをフリーズするのは比較的簡単です。
また、Webワーカーでは簡単に回避できます。特にAngularJS用にそれを処理する人気のあるライブラリはないようです。ただし、実装は簡単なので、最善の方法かもしれません。
まず、サービスを設定しましょう。
.toString()
さて、労働者:
function resolve(index, timeout) { return { data: function($q, $timeout) { var deferred = $q.defer(); $timeout(function () { deferred.resolve(console.log('Data resolve called ' + index)); }, timeout); return deferred.promise; } }; } function configResolves($stateProvide) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html', resolve: resolve(1, 1597) }) // A COMPLEX PRODUCT PAGE .state('main.product', { url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', resolve: resolve(2, 2584) }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } }, resolve: resolve(3, 987) }); }
ここで、通常どおりサービスを挿入し、Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executed
を処理します。 promiseを返す他のサービスメソッドと同じように。重い処理は別のスレッドで実行され、UXに害はありません。
注意すべき点:
.run()
渡されたプロパティで、それは正しく機能しました。解決すると、ビューの読み込みに時間がかかります。フロントエンドアプリの高性能が私たちの主な目標だと思います。アプリがAPIからのデータを待機している間に、ビューの一部をレンダリングすることは問題ではありません。
この設定を検討してください。
this.maxPrice = '100'; this.price = '55'; $scope.$watch('MC.price', function (newVal) { if (newVal || newVal === 0) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } });
コンソール出力は次のようになります。
this.maxPrice = '100'; this.price = '55'; this.priceTemporary = '55'; $scope.$watch('MC.price', function (newVal) { if (!isNaN(newVal)) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } }); var timeoutInstance; $scope.$watch('MC.priceTemporary', function (newVal) { if (!isNaN(newVal)) { if (timeoutInstance) { $timeout.cancel(timeoutInstance); } timeoutInstance = $timeout(function () { $scope.MC.price = newVal; }, 144); } });
これは基本的に次のことを意味します。
これは、ユーザーが出力を見る前に、すべての依存関係を待つ必要があることを意味します。確かに、そのデータが必要です。ビューの前に絶対に必要な場合は、$digest()
に入れてくださいブロック。それ以外の場合は、コントローラーからサービスを呼び出して、ハーフロード状態を適切に処理します。進行中の作業を確認すること(およびコントローラーはすでに実行されているため、実際には進行中です)は、アプリを停止させるよりも優れています。
a)モデルへのスライダーの取り付けなど、ダイジェストループが多すぎる原因
これはAngularJSエラーを引き起こす可能性のある一般的な問題ですが、スライダーの例で説明します。拡張機能が必要だったので、このスライダーライブラリである角度範囲スライダーを使用していました。そのディレクティブには、最小バージョンで次の構文があります。
ng-click
コントローラ内の次のコードを検討してください。
input
そのため、動作が遅くなります。カジュアルな解決策は、入力にタイムアウトを設定することです。ただし、これは必ずしも便利なわけではなく、すべての場合に実際のモデル変更を遅らせたくない場合もあります。
したがって、タイムアウト時に作業モデルを変更するためにバインドされた一時モデルを追加します。
$timeout
およびコントローラー内:
$http
b)$ applyAsyncを使用しない
AngularJSには、$watch
を呼び出すためのポーリングメカニズムがありません。ディレクティブ(例:.$applyAsync()
、$digest()
)、サービス(applyAsync
、$http
)、および評価するメソッド(mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
)を使用するためにのみ実行されます。コードを作成し、後でダイジェストを呼び出します。
何.click()
次の$apply()
まで式の解決を遅らせますかサイクル。タイムアウトが0の後にトリガーされます。これは、実際には約10ミリ秒です。
$scope.$root.$digest()
を使用するには2つの方法があります今。 $rootScope.$digest()
の自動化された方法リクエスト、および残りの手動の方法。
ほぼ同時に返されるすべてのhttpリクエストを1つのダイジェストで解決するには、次のようにします。
$scope.$digest()
手動の方法は、実際にどのように機能するかを示しています。バニラJSイベントリスナーまたはjQuery I AM DIRECTIVE$scope.$applyAsync()
、またはその他の外部ライブラリへのコールバックで実行される関数について考えてみます。実行してモデルを変更した後、まだ$digest()
でラップしていない場合 remove directive
に電話する必要があります(function MainController($rootScope, $scope) { this.removeDirective = function () { $rootScope.$emit('destroyDirective'); }; } function testForApeeScape($rootScope, $timeout) { return { link: function (scope, element, attributes) { var destroyListener = $rootScope.$on('destroyDirective', function () { scope.$destroy(); }); // adding a timeout for the DOM to get ready $timeout(function () { scope.toBeDetached = element.find('p'); }); scope.$on('$destroy', function () { destroyListener(); element.remove(); }); }, template: '
)、または少なくともscope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });
。それ以外の場合、変更は表示されません。
1つのフローでこれを複数回実行すると、実行速度が低下する可能性があります。 … the isolated scope is not available here, look: {{ isolatedModel }}
に電話することを検討してください代わりに式に。それらすべてに対して1つのダイジェストサイクルのみを呼び出すように設定されます。
c)画像の大量処理を行う
パフォーマンスが低下した場合は、Chromeデベロッパーツールのタイムラインを使用して理由を調査できます。このツールについては、間違い#17で詳しく説明します。記録後にタイムライングラフが緑色で占められている場合、パフォーマンスの問題は画像の処理に関連している可能性があります。これは厳密にはAngularJSとは関係ありませんが、AngularJSのパフォーマンスの問題(グラフではほとんど黄色)に加えて発生する可能性があります。フロントエンドエンジニアとして、私たちは完全なエンドプロジェクトについて考える必要があります。
評価する時間を取ってください:
上記の少なくとも3つに「はい」と答えた場合は、緩和することを検討してください。おそらく、さまざまな画像サイズを提供でき、サイズ変更はまったくできません。たぶん、「transform:translateZ(0)」強制GPU処理ハックを追加することができます。または、ハンドラーにrequestAnimationFrameを使用します。
AngularJSでjQueryを使用することは推奨されておらず、避けるべきだとよく耳にします。これらのステートメントの背後にある理由を理解することが不可欠です。私が見る限り、少なくとも3つの理由がありますが、実際のブロッカーではありません。
理由1: jQueryコードを実行するときは、function MainController($interval) { this.foo = { bar: 1 }; this.baz = 1; var that = this; $interval(function () { that.foo.bar++; }, 144); $interval(function () { that.baz++; }, 144); this.quux = [1,2,3]; }
を呼び出す必要があります。あなた自身。多くの場合、 AngularJSソリューション これはAngularJS用に調整されており、jQueryよりもAngular内でより適切に使用できます(例:ng-clickまたはイベントシステム)。
理由2: アプリの構築についての考え方。ナビゲート時にリロードするJavaScriptをWebサイトに追加している場合は、メモリ消費についてあまり心配する必要はありません。シングルページアプリでは、心配する必要はありません。クリーンアップしないと、アプリに数分以上費やすユーザーでパフォーマンスの問題が増大する可能性があります。
理由3: クリーンアップは、実際には実行および分析するのが最も簡単なことではありません。 (ブラウザーで)スクリプトからガベージコレクターを呼び出す方法はありません。最終的には、DOMツリーが切り離される可能性があります。私は例を作成しました(jQueryはindex.htmlにロードされます):
function testDirective() { var postLink = function (scope, element, attrs) { scope.$watch(attrs.watchAttribute, function (newVal) { if (newVal) { // take a look in the console // we can't use the attribute directly console.log(attrs.watchAttribute); // the newVal is evaluated, and it can be used scope.modifiedFooBar = newVal.bar * 10; } }, true); attrs.$observe('observeAttribute', function (newVal) { scope.observed = newVal; }); }; return { link: postLink, templateUrl: '/attributes-demo/test-directive.html' }; }
これは、テキストを出力する単純なディレクティブです。その下にボタンがあり、ディレクティブを手動で破棄するだけです。
したがって、ディレクティブが削除されても、scope.toBeDetachedにはDOMツリーへの参照が残ります。 Chrome開発ツールでは、[プロファイル]タブにアクセスしてから[ヒープスナップショットを取得]タブにアクセスすると、出力に次のように表示されます。
あなたは少数で生きることができます、しかしあなたがトンを持っているならばそれは悪いです。特に、例のように何らかの理由でスコープに保存する場合は特にそうです。 DOM全体がすべてのダイジェストで評価されます。問題のある分離されたDOMツリーは、4つのノードを持つものです。では、これをどのように解決できますか?
attrs.watchAttribute
4つのエントリを持つデタッチされたDOMツリーが削除されます!
この例では、ディレクティブは同じスコープを使用し、スコープにDOM要素を格納します。そのようにそれを示すことは私にとってより簡単でした。変数に格納できるため、必ずしもそれほど悪くなるとは限りません。ただし、その変数または同じ関数スコープの他の変数を参照していたクロージャが存在する場合は、メモリを消費します。
単一の場所で使用されることがわかっているディレクティブが必要な場合、または使用されている環境と競合することが予想されない場合は、分離スコープを使用する必要はありません。最近、再利用可能なコンポーネントを作成する傾向がありますが、コア角度ディレクティブが分離スコープをまったく使用していないことをご存知ですか?
主な理由は2つあります。要素に2つの分離されたスコープディレクティブを適用できないことと、ネスト/継承/イベント処理で問題が発生する可能性があることです。特にトランスクルージョンに関して-効果はあなたが期待するものではないかもしれません。
したがって、これは失敗します。
scope.$watch()
また、ディレクティブを1つだけ使用した場合でも、分離されたスコープモデルも、isolatedScopeDirectiveでブロードキャストされたイベントもAnotherControllerで使用できないことに気付くでしょう。悲しいことに、トランスクルージョンマジックを曲げて使用して機能させることができますが、ほとんどのユースケースでは、分離する必要はありません。
MC.foo
だから、今2つの質問:
2つの方法があり、どちらの方法でも、属性に値を渡します。このMainControllerについて考えてみましょう。
$watch()
これはこのビューを制御します。
MC.foo
「watch-attribute」は補間されていないことに注意してください。 JSマジックにより、すべて機能します。ディレクティブの定義は次のとおりです。
$parse
$eval
に注意してください$destroy
に渡されます引用符なし!つまり、実際に$ watchに渡されたのは、文字列function cleanMeUp($interval, $rootScope, $timeout) { var postLink = function (scope, element, attrs) { var rootModelListener = $rootScope.$watch('someModel', function () { // do something }); var myInterval = $interval(function () { // do something in intervals }, 2584); var myTimeout = $timeout(function () { // defer some action here }, 1597); scope.domElement = element; $timeout(function () { // calling $destroy manually for testing purposes scope.$destroy(); }, 987); // here is where the cleanup happens scope.$on('$destroy', function () { // disable the listener rootModelListener(); // cancel the interval and timeout $interval.cancel(myInterval); $timeout.cancel(myTimeout); // nullify the DOM-bound model scope.domElement = null; }); element.on('$destroy', function () { // this is a jQuery event // clean up all vanilla JavaScript / jQuery artifacts here // respectful jQuery plugins have $destroy handlers, // that is the reason why this event is emitted... // follow the standards. }); };
でした。ただし、$destroy
に渡される文字列があるため、機能します。スコープに対して評価され、$digest()
スコープで利用可能です。これは、AngularJSコアディレクティブで属性を監視する最も一般的な方法でもあります。
テンプレートについてはgithubのコードを参照し、{{ model }}
を調べてください。および
さらに素晴らしいために。
AngularJSはあなたに代わっていくつかの作業を行いますが、すべてではありません。以下は手動でクリーンアップする必要があります。
vastArray
イベントこれを手動で行わないと、予期しない動作やメモリリークが発生します。さらに悪いことに、これらはすぐには表示されませんが、最終的には忍び寄ります。マーフィーの法則。
驚くべきことに、AngularJSはこれらすべてに対処するための便利な方法を提供します。
item.velocity
jQueryに注意してください{{ someModel }}
イベント。 AngularJSと同じように呼ばれますが、個別に処理されます。スコープ$ watchersはjQueryイベントに反応しません。
これは今では非常に簡単なはずです。ここで理解すべきことが1つあります:ng-if
。バインディングng-repeat
ごとに、AngularJSはウォッチャーを作成します。すべての消化段階で、そのような各結合が評価され、前の値と比較されます。これはダーティチェックと呼ばれ、$ digestが行うことです。最後のチェック以降に値が変更された場合、ウォッチャーコールバックが発生します。そのウォッチャーコールバックがモデル($ scope変数)を変更する場合、例外がスローされると、新しい$ digestサイクルが発生します(最大10)。
式が複雑でない限り、ブラウザには何千ものバインディングがあっても問題はありません。 「何人のウォッチャーがいても大丈夫」の一般的な答えは2000です。
では、どうすればウォッチャーの数を制限できますか?スコープモデルが変更されるとは思わないときに、スコープモデルを監視しないこと。ワンタイムバインディングがコアになっているため、AngularJS1.3以降はかなり簡単です。
$watch()
後$Watchers
および$watch()
一度評価されると、二度と変更されることはありません。配列にフィルターを適用することはできますが、それらは問題なく機能します。配列自体が評価されないだけです。多くの場合、それは勝利です。
このAngularJSエラーは、間違い9.bと13ですでに部分的にカバーされています。これはより完全な説明です。 AngularJSは、ウォッチャーへのコールバック関数の結果としてDOMを更新します。すべてのバインディング、つまりディレクティブ$rootScope.$digest()
ウォッチャーを設定しますが、ウォッチャーは$scope
のような他の多くのディレクティブにも設定されますおよび$watch()
。ソースコードを見てください、それは非常に読みやすいです。ウォッチャーは手動で設定することもできますが、おそらく自分で少なくとも数回は設定したことがあるでしょう。
$digest()
ersはスコープにバインドされています。 .$apply
.$applyAsync
のスコープに対して評価される文字列を受け取ることができますにバインドされていました。また、機能を評価することもできます。また、コールバックも受けます。したがって、.$evalAsync
の場合が呼び出されると、登録されているすべてのモデル(つまり、$digest()
変数)が評価され、以前の値と比較されます。値が一致しない場合、$digest()
へのコールバック実行されます。
モデルの値が変更された場合でも、コールバックは次のダイジェストフェーズまで発生しないことを理解することが重要です。これは、いくつかのダイジェストサイクルで構成されている可能性があるため、「フェーズ」と呼ばれます。ウォッチャーのみがスコープモデルを変更した場合、別のダイジェストサイクルが実行されます。
だが describe('some module', function () { it('should call the name-it service…', function () { // leave this empty for now }); ... });
ポーリングされません 。コアディレクティブ、サービス、メソッドなどから呼び出されます。'use strict'; var gulp = require('gulp'); var args = require('yargs').argv; var browserSync = require('browser-sync'); var karma = require('gulp-karma'); var protractor = require('gulp-protractor').protractor; var webdriverUpdate = require('gulp-protractor').webdriver_update; function test() { // Be sure to return the stream // NOTE: Using the fake './foobar' so as to run the files // listed in karma.conf.js INSTEAD of what was passed to // gulp.src ! return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'run' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); this.emit('end'); //instead of erroring the stream, end it }); } function tdd() { return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'start' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); // this.emit('end'); // not ending the stream here }); } function runProtractor () { var argument = args.suite || 'all'; // NOTE: Using the fake './foobar' so as to run the files // listed in protractor.conf.js, instead of what was passed to // gulp.src return gulp.src('./foobar') .pipe(protractor({ configFile: 'test/protractor.conf.js', args: ['--suite', argument] })) .on('error', function (err) { // Make sure failed tests cause gulp to exit non-zero throw err; }) .on('end', function () { // Close browser sync server browserSync.exit(); }); } gulp.task('tdd', tdd); gulp.task('test', test); gulp.task('test-e2e', ['webdriver-update'], runProtractor); gulp.task('webdriver-update', webdriverUpdate);
、console.log()
、debugInfo
、または最終的に呼び出すものを呼び出さないカスタム関数からモデルを変更した場合$(document.body).scope().$root
、バインディングは更新されません。
ちなみに、$($0).scope()
のソースコード実際にはかなり複雑です。それにもかかわらず、陽気な警告がそれを補うので、それは読む価値があります。
フロントエンド開発のトレンドを追い、私のように少し怠惰な場合は、おそらくすべてを手作業でやろうとはしません。すべての依存関係を追跡し、さまざまな方法でファイルのセットを処理し、ファイルを保存するたびにブラウザーをリロードします。開発には、コーディングだけではありません。
そのため、アプリの提供方法によっては、bowerやnpmを使用している可能性があります。うなり声、一口、またはブランチを使用している可能性があります。またはbash、これもクールです。実際、Yeomanジェネレーターを使用して最新のプロジェクトを開始した可能性があります。
これは質問につながります:あなたはあなたのインフラストラクチャが実際に何をしているのかというプロセス全体を理解していますか?特に、接続Webサーバーのライブリロード機能を修正するために何時間も費やした場合は特に、持っているものが必要ですか?
少し時間を取って、必要なものを評価してください。これらのツールはすべてあなたを助けるためにここにあるだけであり、それらを使用することに対する他の報酬はありません。私が話す経験豊富な開発者は、物事を単純化する傾向があります。
テストでは、コードにAngularJSエラーメッセージが含まれることはありません。彼らが行うことは、チームが常にリグレッションの問題に遭遇しないようにすることです。
ここでは、ユニットテストについて具体的に書いています。e2eテストよりもユニットテストの方が重要だと感じているからではなく、実行速度がはるかに速いからです。これから説明するプロセスは非常に楽しいものであることを認めなければなりません。
たとえば、の実装としてのテスト駆動開発。 gulp-karma runnerは、基本的にすべてのファイル保存ですべての単体テストを実行します。テストを書く私のお気に入りの方法は、最初に空の保証を書くことです。
angular.reloadWithDebugInfo()
その後、実際のコードを記述またはリファクタリングしてから、テストに戻り、保証に実際のテストコードを入力します。
ターミナルでTDDタスクを実行すると、プロセスが約100%高速化されます。ユニットテストは、たくさんある場合でも、数秒で実行されます。テストファイルを保存するだけで、ランナーがそれを取得し、テストを評価して、即座にフィードバックを提供します。
e2eテストでは、プロセスははるかに遅くなります。私のアドバイス-e2eテストをテストスイートに分割し、一度に1つずつ実行します。分度器はそれらをサポートしています。以下は、テストタスクに使用するコードです(私はgulpが好きです)。
var injector = $(document.body).injector(); var someService = injector.get('someService');
A-クロムブレークポイント
Chrome開発ツールを使用すると、ブラウザに読み込まれたファイルの特定の場所をポイントし、その時点でコードの実行を一時停止し、その時点から利用可能なすべての変数を操作できます。それはたくさんです!この機能では、コードを追加する必要はまったくありません。すべてが開発ツールで行われます。
すべての変数にアクセスできるだけでなく、呼び出しスタック、印刷スタックトレースなども表示されます。縮小されたファイルで動作するように構成することもできます。それについて読む ここに 。
同様のランタイムアクセスを取得する方法は他にもあります。 Ng-init
を追加する呼び出します。ただし、ブレークポイントはより高度です。
AngularJSを使用すると、DOM要素を介してスコープにアクセスし(ng-if
が有効になっている場合)、コンソールを介して利用可能なサービスを挿入することもできます。コンソールで次のことを考慮してください。
ng-repeat
または、インスペクターの要素をポイントしてから、次のようにします。
var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });
debugInfoが有効になっていない場合でも、次のことができます。
var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }];
そして、リロード後に利用できるようにします。
コンソールからサービスを挿入して操作するには、次のことを試してください。
$watch
B-クロームタイムライン
開発ツールに付属するもう1つの優れたツールは、タイムラインです。これにより、使用中のアプリのライブパフォーマンスを記録および分析できます。出力には、特に、メモリ使用量、フレームレート、およびCPUを占有するさまざまなプロセス(ロード、スクリプト、レンダリング、およびペイント)の分析が表示されます。
アプリのパフォーマンスが低下した場合は、[タイムライン]タブでその原因を特定できる可能性があります。パフォーマンスの問題を引き起こしたアクションを記録して、何が起こるかを確認してください。ウォッチャーが多すぎますか?黄色のバーが多くのスペースを占めているのがわかります。メモリリーク?時間の経過とともに消費されたメモリの量をグラフで確認できます。
詳細な説明: https://developer.chrome.com/devtools/docs/timeline
C-iOSおよびAndroidでアプリをリモートで検査する
ハイブリッドアプリまたはレスポンシブウェブアプリを開発している場合は、デバイスのコンソール、DOMツリー、およびChromeまたはSafari開発ツールから利用できる他のすべてのツールにアクセスできます。これには、WebViewとUIWebViewが含まれます。
まず、ホスト0.0.0.0でWebサーバーを起動して、ローカルネットワークからアクセスできるようにします。設定でWebインスペクターを有効にします。次に、デバイスをデスクトップに接続し、通常の「localhost」の代わりにマシンのIPを使用して、ローカル開発ページにアクセスします。これで、デスクトップのブラウザからデバイスを利用できるようになります。
ここに AndroidとiOSの詳細な手順は、Googleから簡単に見つけることができます。
私は最近、いくつかのクールな経験をしました browserSync 。これはlivereloadと同じように機能しますが、browserSyncを介して同じページを表示しているすべてのブラウザーを実際に同期します。これには、スクロール、ボタンのクリックなどのユーザー操作が含まれます。デスクトップからiPadのページを制御しながら、iOSアプリのログ出力を見ていました。それはうまくいきました!
// when in doubt, comment it out! :)
は、その音からすると、
|_+_|に似ているはずです。と
|_+_|でしょ?なぜそれを使用すべきではないというコメントがドキュメントにあるのか疑問に思ったことはありますか?意外だった私見!ディレクティブがモデルを初期化することを期待します。それはそれが行うことでもありますが…それは別の方法で実装されます。つまり、属性値を監視しません。 AngularJSのソースコードを閲覧する必要はありません。それをお届けします。
|_+_|
あなたが期待するよりも少ないですか?厄介なディレクティブ構文以外に、かなり読みやすいですね。 6行目はそれがすべてであるものです。
ng-showと比較してください。
|_+_|
繰り返しますが、6行目です。
|_+_|がありますそこで、それがこのディレクティブを動的にするものです。 AngularJSソースコードでは、すべてのコードの大部分は、最初からほとんど読めるコードを説明するコメントです。 AngularJSについて学ぶのに最適な方法だと思います。
最も一般的なAngularJSの間違いをカバーするこのガイドは、他のガイドのほぼ2倍の長さです。当然そのようになりました。高品質のJavaScriptフロントエンドエンジニアに対する需要は非常に高いです。 AngularJSは 今とても暑い 、そしてそれは数年の間最も人気のある開発ツールの中で安定した位置を保持しています。 AngularJS 2.0が開発中であるため、今後数年間はおそらく支配的になるでしょう。
フロントエンド開発の優れている点は、非常にやりがいがあることです。私たちの仕事は即座に目に見え、人々は私たちが提供する製品と直接対話します。学習に費やした時間 JavaScript 、そしてJavaScript言語に焦点を当てるべきだと私は信じていますが、これは非常に良い投資です。それはインターネットの言語です。競争は超激しいです!私たちの焦点は1つあります。それは、ユーザーエクスペリエンスです。成功するには、すべてをカバーする必要があります。
これらの例で使用されているソースコードは、からダウンロードできます。 GitHub 。気軽にダウンロードして、自分だけのものにしてください。
私に最も影響を与えた4人の出版開発者にクレジットを与えたかった:
また、FreeNode #angularjsおよび#javascriptチャネルのすべての素晴らしい人々に、多くの優れた会話と継続的なサポートに感謝したいと思います。
そして最後に、常に覚えておいてください:
|_+_|
フロントエンド開発のトレンドを追い、私のように少し怠惰な場合は、おそらくすべてを手作業でやろうとはしません。すべての依存関係を追跡し、さまざまな方法でファイルのセットを処理し、ファイルを保存するたびにブラウザーをリロードします。開発には、コーディングだけではありません。
そのため、アプリの提供方法によっては、bowerやnpmを使用している可能性があります。うなり声、一口、またはブランチを使用している可能性があります。またはbash、これもクールです。実際、Yeomanジェネレーターを使用して最新のプロジェクトを開始した可能性があります。
これは質問につながります:あなたはあなたのインフラストラクチャが実際に何をしているのかというプロセス全体を理解していますか?特に、接続Webサーバーのライブリロード機能を修正するために何時間も費やした場合は特に、持っているものが必要ですか?
少し時間を取って、必要なものを評価してください。これらのツールはすべてあなたを助けるためにここにあるだけであり、それらを使用することに対する他の報酬はありません。私が話す経験豊富な開発者は、物事を単純化する傾向があります。
テストでは、コードにAngularJSエラーメッセージが含まれることはありません。彼らが行うことは、チームが常にリグレッションの問題に遭遇しないようにすることです。
ここでは、ユニットテストについて具体的に書いています。e2eテストよりもユニットテストの方が重要だと感じているからではなく、実行速度がはるかに速いからです。これから説明するプロセスは非常に楽しいものであることを認めなければなりません。
たとえば、の実装としてのテスト駆動開発。 gulp-karma runnerは、基本的にすべてのファイル保存ですべての単体テストを実行します。テストを書く私のお気に入りの方法は、最初に空の保証を書くことです。
angular.reloadWithDebugInfo()
その後、実際のコードを記述またはリファクタリングしてから、テストに戻り、保証に実際のテストコードを入力します。
ターミナルでTDDタスクを実行すると、プロセスが約100%高速化されます。ユニットテストは、たくさんある場合でも、数秒で実行されます。テストファイルを保存するだけで、ランナーがそれを取得し、テストを評価して、即座にフィードバックを提供します。
e2eテストでは、プロセスははるかに遅くなります。私のアドバイス-e2eテストをテストスイートに分割し、一度に1つずつ実行します。分度器はそれらをサポートしています。以下は、テストタスクに使用するコードです(私はgulpが好きです)。
var injector = $(document.body).injector(); var someService = injector.get('someService');
A-クロムブレークポイント
Chrome開発ツールを使用すると、ブラウザに読み込まれたファイルの特定の場所をポイントし、その時点でコードの実行を一時停止し、その時点から利用可能なすべての変数を操作できます。それはたくさんです!この機能では、コードを追加する必要はまったくありません。すべてが開発ツールで行われます。
すべての変数にアクセスできるだけでなく、呼び出しスタック、印刷スタックトレースなども表示されます。縮小されたファイルで動作するように構成することもできます。それについて読む ここに 。
同様のランタイムアクセスを取得する方法は他にもあります。 Ng-init
を追加する呼び出します。ただし、ブレークポイントはより高度です。
AngularJSを使用すると、DOM要素を介してスコープにアクセスし(ng-if
が有効になっている場合)、コンソールを介して利用可能なサービスを挿入することもできます。コンソールで次のことを考慮してください。
ng-repeat
または、インスペクターの要素をポイントしてから、次のようにします。
var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });
debugInfoが有効になっていない場合でも、次のことができます。
var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }];
そして、リロード後に利用できるようにします。
コンソールからサービスを挿入して操作するには、次のことを試してください。
$watch
B-クロームタイムライン
開発ツールに付属するもう1つの優れたツールは、タイムラインです。これにより、使用中のアプリのライブパフォーマンスを記録および分析できます。出力には、特に、メモリ使用量、フレームレート、およびCPUを占有するさまざまなプロセス(ロード、スクリプト、レンダリング、およびペイント)の分析が表示されます。
アプリのパフォーマンスが低下した場合は、[タイムライン]タブでその原因を特定できる可能性があります。パフォーマンスの問題を引き起こしたアクションを記録して、何が起こるかを確認してください。ウォッチャーが多すぎますか?黄色のバーが多くのスペースを占めているのがわかります。メモリリーク?時間の経過とともに消費されたメモリの量をグラフで確認できます。
詳細な説明: https://developer.chrome.com/devtools/docs/timeline
C-iOSおよびAndroidでアプリをリモートで検査する
ハイブリッドアプリまたはレスポンシブウェブアプリを開発している場合は、デバイスのコンソール、DOMツリー、およびChromeまたはSafari開発ツールから利用できる他のすべてのツールにアクセスできます。これには、WebViewとUIWebViewが含まれます。
まず、ホスト0.0.0.0でWebサーバーを起動して、ローカルネットワークからアクセスできるようにします。設定でWebインスペクターを有効にします。次に、デバイスをデスクトップに接続し、通常の「localhost」の代わりにマシンのIPを使用して、ローカル開発ページにアクセスします。これで、デスクトップのブラウザからデバイスを利用できるようになります。
ここに AndroidとiOSの詳細な手順は、Googleから簡単に見つけることができます。
私は最近、いくつかのクールな経験をしました browserSync 。これはlivereloadと同じように機能しますが、browserSyncを介して同じページを表示しているすべてのブラウザーを実際に同期します。これには、スクロール、ボタンのクリックなどのユーザー操作が含まれます。デスクトップからiPadのページを制御しながら、iOSアプリのログ出力を見ていました。それはうまくいきました!
aws認定ソリューションアーキテクト認定
// when in doubt, comment it out! :)
は、その音からすると、
|_+_|に似ているはずです。と
|_+_|でしょ?なぜそれを使用すべきではないというコメントがドキュメントにあるのか疑問に思ったことはありますか?意外だった私見!ディレクティブがモデルを初期化することを期待します。それはそれが行うことでもありますが…それは別の方法で実装されます。つまり、属性値を監視しません。 AngularJSのソースコードを閲覧する必要はありません。それをお届けします。
|_+_|
あなたが期待するよりも少ないですか?厄介なディレクティブ構文以外に、かなり読みやすいですね。 6行目はそれがすべてであるものです。
ng-showと比較してください。
|_+_|
繰り返しますが、6行目です。
|_+_|がありますそこで、それがこのディレクティブを動的にするものです。 AngularJSソースコードでは、すべてのコードの大部分は、最初からほとんど読めるコードを説明するコメントです。 AngularJSについて学ぶのに最適な方法だと思います。
最も一般的なAngularJSの間違いをカバーするこのガイドは、他のガイドのほぼ2倍の長さです。当然そのようになりました。高品質のJavaScriptフロントエンドエンジニアに対する需要は非常に高いです。 AngularJSは 今とても暑い 、そしてそれは数年の間最も人気のある開発ツールの中で安定した位置を保持しています。 AngularJS 2.0が開発中であるため、今後数年間はおそらく支配的になるでしょう。
フロントエンド開発の優れている点は、非常にやりがいがあることです。私たちの仕事は即座に目に見え、人々は私たちが提供する製品と直接対話します。学習に費やした時間 JavaScript 、そしてJavaScript言語に焦点を当てるべきだと私は信じていますが、これは非常に良い投資です。それはインターネットの言語です。競争は超激しいです!私たちの焦点は1つあります。それは、ユーザーエクスペリエンスです。成功するには、すべてをカバーする必要があります。
これらの例で使用されているソースコードは、からダウンロードできます。 GitHub 。気軽にダウンロードして、自分だけのものにしてください。
私に最も影響を与えた4人の出版開発者にクレジットを与えたかった:
また、FreeNode #angularjsおよび#javascriptチャネルのすべての素晴らしい人々に、多くの優れた会話と継続的なサポートに感謝したいと思います。
そして最後に、常に覚えておいてください:
|_+_|