أكثر 18 خطأ شيوعًا ارتكبها مطورو AngularJS

نشرت: 2022-03-11

تتطلب تطبيقات الصفحة الواحدة من مطوري الواجهة الأمامية أن يصبحوا مهندسي برمجيات أفضل. لم تعد CSS و HTML مصدر القلق الأكبر بعد الآن ، في الواقع ، لم يعد هناك مصدر قلق واحد فقط. يحتاج مطور الواجهة الأمامية إلى التعامل مع XHRs ومنطق التطبيق (النماذج والعروض ووحدات التحكم) والأداء والرسوم المتحركة والأنماط والهيكل وتحسين محركات البحث والتكامل مع الخدمات الخارجية. والنتيجة التي تظهر من كل هؤلاء مجتمعة هي تجربة المستخدم (UX) التي يجب أن تكون لها الأولوية دائمًا.

AngularJS هو إطار عمل قوي للغاية. إنه ثالث أكثر المستودعات المميزة بنجمة على GitHub. ليس من الصعب البدء في استخدامه ، ولكن الأهداف التي يُقصد بها تحقيق تتطلب الفهم. لم يعد بإمكان مطوري AngularJS تجاهل استهلاك الذاكرة ، لأنه لن يتم إعادة ضبطه أثناء التنقل بعد الآن. هذه هي طليعة تطوير الويب. فلنحتضنه!

أخطاء AngularJS الشائعة

الخطأ الشائع الأول: الوصول إلى النطاق من خلال DOM

هناك عدد قليل من تعديلات التحسين الموصى بها للإنتاج. واحد منهم هو تعطيل معلومات التصحيح.

DebugInfoEnabled هو إعداد يتم تعيينه افتراضيًا على true ، ويسمح بالوصول إلى النطاق من خلال عقد DOM. إذا كنت ترغب في تجربة ذلك من خلال وحدة تحكم JavaScript ، فحدد عنصر DOM وقم بالوصول إلى نطاقه باستخدام:

 angular.element(document.body).scope()

يمكن أن يكون مفيدًا حتى عند عدم استخدام jQuery مع CSS الخاص به ، ولكن لا ينبغي استخدامه خارج وحدة التحكم. والسبب هو أنه عند تعيين $compileProvider.debugInfoEnabled إلى false ، فإن استدعاء .scope() على عقدة DOM سيعود إلى undefined .

هذا هو أحد الخيارات القليلة الموصى بها للإنتاج.

يرجى ملاحظة أنه لا يزال بإمكانك الوصول إلى النطاق من خلال وحدة التحكم ، حتى أثناء الإنتاج. اتصل angular.reloadWithDebugInfo() من وحدة التحكم وسيقوم التطبيق بذلك.

الخطأ الشائع الثاني: عدم وجود نقطة هناك

ربما تكون قد قرأت أنه إذا لم تكن لديك نقطة في نموذجك ، فأنت تفعل ذلك بشكل خاطئ. عندما يتعلق الأمر بالميراث ، غالبًا ما يكون هذا البيان صحيحًا. النطاقات لها نموذج أولي للوراثة ، نموذجي لجافا سكريبت ، والنطاقات المتداخلة شائعة في AngularJS. تقوم العديد من التوجيهات بإنشاء نطاقات فرعية مثل ngRepeat و ngIf و ngController . عند حل نموذج ، يبدأ البحث في النطاق الحالي ويمر عبر كل نطاق رئيسي ، وصولاً إلى $rootScope .

ولكن عند تحديد قيمة جديدة ، فإن ما يحدث يعتمد على نوع النموذج (المتغير) الذي نريد تغييره. إذا كان النموذج بدائيًا ، فسينشئ النطاق الفرعي نموذجًا جديدًا. ولكن إذا كان التغيير يتعلق بخاصية كائن نموذج ، فإن البحث في النطاقات الأصلية سيجد الكائن المشار إليه ويغير خاصيته الفعلية. لن يتم تعيين نموذج جديد في النطاق الحالي ، لذلك لن يحدث إخفاء:

 function MainController($scope) { $scope.foo = 1; $scope.bar = {innerProperty: 2}; } angular.module('myApp', []) .controller('MainController', MainController);
 <div ng-controller="MainController"> <p>OUTER SCOPE:</p> <p>{{ foo }}</p> <p>{{ bar.innerProperty }}</p> <div ng-if="foo"> <!— ng-if creates a new scope —> <p>INNER SCOPE</p> <p>{{ foo }}</p> <p>{{ bar.innerProperty }}</p> <button ng-click="foo = 2">Set primitive</button> <button ng-click="bar.innerProperty = 3">Mutate object</button> </div> </div>

سيؤدي النقر فوق الزر المسمى "Set primitive" إلى تعيين foo في النطاق الداخلي على 2 ، ولكن لن يتغير foo في النطاق الخارجي.

سيؤدي النقر فوق الزر المسمى "تغيير الكائن" إلى تغيير خاصية الشريط من النطاق الأصلي. نظرًا لعدم وجود متغير في النطاق الداخلي ، فلن يحدث تظليل ، وستكون القيمة المرئية للشريط 3 في كلا النطاقين.

هناك طريقة أخرى للقيام بذلك وهي الاستفادة من حقيقة الإشارة إلى النطاقات الأصلية ونطاق الجذر من كل نطاق. يمكن استخدام كائنات $parent و $root للوصول إلى النطاق الأصلي و $rootScope ، على التوالي ، مباشرةً من طريقة العرض. قد تكون طريقة قوية ، لكنني لست من المعجبين بها بسبب مشكلة استهداف نطاق معين في الدفق. هناك طريقة أخرى لتعيين الخصائص الخاصة بالنطاق والوصول إليها - باستخدام صياغة controllerAs .

الخطأ الشائع الثالث: عدم استخدام وحدة تحكم كنحو

الطريقة البديلة والأكثر فاعلية لتعيين النماذج لاستخدام كائن تحكم بدلاً من نطاق $ المحقون. بدلاً من حقن النطاق ، يمكننا تحديد نماذج مثل هذا:

 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() }
 <div> <p>OUTER SCOPE:</p> <p>{{ MC.foo }}</p> <p>{{ MC.bar.someProperty }}</p> <div ng-if="test1"> <p>INNER SCOPE</p> <p>{{ MC.foo }}</p> <p>{{ MC.bar.someProperty }}</p> <button ng-click="MC.foo = 3">Change MC.foo</button> <button ng-click="MC.bar.someProperty = 5">Change MC.bar.someProperty</button> </div> </div>

هذا أقل إرباكًا. خاصة عندما يكون هناك العديد من النطاقات المتداخلة ، كما هو الحال مع الحالات المتداخلة.

هناك المزيد لوحدة التحكم

الخطأ الشائع الرابع: عدم الاستخدام الكامل لوحدة التحكم

هناك بعض المحاذير حول كيفية تعرض كائن وحدة التحكم. إنه في الأساس كائن تم تعيينه في نطاق وحدة التحكم ، تمامًا مثل النموذج العادي.

إذا كنت بحاجة إلى مشاهدة خاصية كائن وحدة التحكم ، فيمكنك مشاهدة وظيفة ولكنك لست مطالبًا بذلك. هنا مثال:

 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'; } }

ومع ذلك ، لكي تتمكن من القيام بذلك ، يجب أن تكون متسقًا مع الاختصار الذي تستخدمه لوحدات التحكم. هناك ثلاث طرق على الأقل لضبطه. لقد رأيت بالفعل أول واحد:

 <div ng-controller="MainController as MC"> … </div>

ومع ذلك ، إذا كنت تستخدم 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 testForToptal() { return { controller: 'AnotherController as AC', template: '<p>{{ AC.text }}</p>' }; } angular.module('myApp', []) .controller('AnotherController', AnotherController) .directive('testForToptal', testForToptal);

الطريقة الأخرى للتعليق صالحة أيضًا ، على الرغم من أنها أقل إيجازًا:

 function testForToptal() { return { controller: 'AnotherController', controllerAs: 'AC', template: '<p>{{ AC.text }}</p>' }; }

الخطأ الشائع رقم 5: عدم استخدام طرق العرض المسماة مع UI-ROUTER للحصول على الطاقة "

كان حل التوجيه الفعلي لـ AngularJS ، حتى الآن ، هو ui-router . تمت إزالته من النواة منذ فترة ، كانت الوحدة النمطية ngRoute أساسية جدًا لتوجيه أكثر تعقيدًا.

هناك NgRouter جديد في طريقه ، لكن المؤلفين ما زالوا يعتبرونه مبكرًا جدًا للإنتاج. عندما أكتب هذا ، يكون Angular المستقر 1.3.15 وصخور ui-router .

الأسباب الرئيسية:

  • رهيبة الدولة التعشيش
  • تجريد المسار
  • المعلمات الاختيارية والمطلوبة

سأغطي هنا تداخل الحالة لتجنب أخطاء AngularJS.

فكر في هذا على أنه حالة استخدام معقدة ولكنها قياسية. هناك تطبيق يحتوي على عرض الصفحة الرئيسية وعرض المنتج. تشتمل طريقة عرض المنتج على ثلاثة أقسام منفصلة: المقدمة والأداة والمحتوى. نريد استمرار الأداة وعدم إعادة التحميل عند التبديل بين الحالة. ولكن يجب إعادة تحميل المحتوى.

ضع في اعتبارك بنية صفحة فهرس منتج HTML التالية:

 <body> <header> <!-- SOME STATIC HEADER CONTENT --> </header> <section class="main"> <div class="page-content"> <div class="row"> <div class="col-xs-12"> <section class="intro"> <h2>SOME PRODUCT SPECIFIC INTRO</h2> </section> </div> </div> <div class="row"> <div class="col-xs-3"> <section class="widget"> <!-- some widget, which should never reload --> </section> </div> <div class="col-xs-9"> <section class="content"> <div class="product-content"> <h2>Product title</h2> <span>Context-specific content</span> </div> </section> </div> </div> </div> </section> <footer> <!-- SOME STATIC HEADER CONTENT --> </footer> </body>

هذا شيء يمكن أن نحصل عليه من مبرمج HTML ، ونحتاج الآن إلى فصله إلى ملفات وحالات. أوافق عمومًا على الاتفاقية التي تنص على وجود حالة رئيسية مجردة ، والتي تحافظ على البيانات العالمية إذا لزم الأمر. استخدم ذلك بدلاً من $ rootScope. ستحافظ الحالة الرئيسية أيضًا على HTML ثابت مطلوب في كل صفحة. أحافظ على ملف index.html نظيفًا.

 <!— index.html —> <body> <div ui-view></div> </body>
 <!— main.html —> <header> <!-- SOME STATIC HEADER CONTENT --> </header> <section class="main"> <div ui-view></div> </section> <footer> <!-- SOME STATIC HEADER CONTENT --> </footer>

ثم دعنا نرى صفحة فهرس المنتج:

 <div class="page-content"> <div class="row"> <div class="col-xs-12"> <section class="intro"> <div ui-view="intro"></div> </section> </div> </div> <div class="row"> <div class="col-xs-3"> <section class="widget"> <div ui-view="widget"></div> </section> </div> <div class="col-xs-9"> <section class="content"> <div ui-view="content"></div> </section> </div> </div> </div>

كما ترى ، تحتوي صفحة فهرس المنتج على ثلاث طرق عرض مسماة. واحد للمقدمة والآخر للأداة والآخر للمنتج. نلبي المواصفات! لنقم الآن بإعداد التوجيه:

 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 ؟ يتم إعادة تحميل المحتوى والأداة ، لكننا نريد فقط إعادة تحميل المحتوى. كان هذا يمثل مشكلة ، وقد أنشأ المطورون بالفعل أجهزة توجيه تدعم هذه الوظيفة فقط. كان أحد أسماء ذلك المشاهدات الثابتة . لحسن الحظ ، يدعم 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' } } });

من خلال نقل تعريف الحالة إلى العرض الأصلي ، وهو أيضًا مجرد تجريدي ، يمكننا الحفاظ على عرض الطفل من إعادة التحميل عند تبديل عناوين url التي تؤثر عادةً على أشقاء ذلك الطفل. بالطبع ، يمكن أن تكون الأداة عبارة عن توجيه بسيط. لكن النقطة المهمة هي أنه يمكن أن يكون أيضًا حالة متداخلة معقدة أخرى.

هناك طريقة أخرى للقيام بذلك من خلال استخدام $urlRouterProvider.deferIntercept() ، لكنني أعتقد أن استخدام تكوين الحالة أفضل بالفعل. إذا كنت مهتمًا باعتراض المسارات ، فقد كتبت تعليميًا صغيرًا عن StackOverflow.

الخطأ الشائع السادس: التصريح عن كل شيء في العالم الزاوي باستخدام وظائف مجهولة

هذا الخطأ ذو عيار أخف ، وهو يتعلق بالأسلوب أكثر من تجنب رسائل خطأ AngularJS. ربما لاحظت سابقًا أنني نادرًا ما أنقل وظائف مجهولة إلى تصريحات الزاوية الداخلية. عادةً ما أقوم بتعريف الوظيفة أولاً ثم تمريرها.

هذا يتعلق بأكثر من مجرد وظائف. لقد حصلت على هذا النهج من قراءة أدلة الأسلوب ، خاصةً في Airbnb و Todd Motto. أعتقد أن هناك العديد من المزايا ولا عيوب تقريبًا.

بادئ ذي بدء ، يمكنك التعامل مع وظائفك وكائناتك وتحويلها بسهولة أكبر إذا تم تخصيصها لمتغير. ثانيًا ، الكود أنظف ويمكن تقسيمه بسهولة إلى ملفات. هذا يعني قابلية الصيانة. إذا كنت لا تريد تلويث مساحة الاسم العالمية ، فقم بلف كل ملف في IIFEs. السبب الثالث هو قابلية الاختبار. ضع في اعتبارك هذا المثال:

 '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 من الأسلوب العام 1 ، لكن لماذا نفعل ذلك لأنه مكشوف؟ ألن يكون من الأسهل مجرد التجسس على الطريقة الحالية؟ ومع ذلك ، فإن الطريقة هي في الواقع وظيفة أخرى - غلاف رقيق. ألق نظرة على هذا النهج:

 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; }

لا يتعلق الأمر بالأسلوب فقط ، لأن الكود في الواقع أكثر قابلية لإعادة الاستخدام والأصطلاحية. المطور يحصل على قوة تعبيرية أكبر. إن تقسيم كل التعليمات البرمجية إلى كتل قائمة بذاتها يجعل الأمر أسهل.

الخطأ الشائع رقم 7: القيام بمعالجة ثقيلة في الزاوية AKA باستخدام العمال

في بعض السيناريوهات ، قد يكون من الضروري معالجة مجموعة كبيرة من الكائنات المعقدة عن طريق تمريرها عبر مجموعة من المرشحات والديكورات وأخيراً خوارزمية الفرز. إحدى حالات الاستخدام هي عندما يجب أن يعمل التطبيق في وضع عدم الاتصال أو عندما يكون أداء عرض البيانات هو المفتاح. ونظرًا لأن JavaScript ذات ترابط واحد ، فمن السهل نسبيًا تجميد المتصفح.

من السهل أيضًا تجنبه مع العاملين على الويب. لا يبدو أن هناك أي مكتبات شائعة تتعامل مع ذلك خصيصًا لـ AngularJS. قد يكون من الأفضل ، لأن التنفيذ سهل.

أولاً ، دعنا نجهز الخدمة:

 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 < items.length; i++) { // some heavy processing // itemsArray is populated, etc. } itemsArray.sort(function (a, b) { if (a.sum > b.sum) { return -1; } else if (a.sum < b.sum) { return 1; } else { return 0; } }); return itemsArray; } self.addEventListener('message', function (e) { var reply = { ready: true }; if (e.data && e.data.items && e.data.items.length) { reply.items = scoringFunction(e.data.items, e.data.weights); } self.postMessage(reply); }, false);

الآن ، قم بحقن الخدمة كالمعتاد وتعامل مع scoringService.scoreItems() كما تفعل مع أي طريقة خدمة ترجع الوعد. سيتم تنفيذ المعالجة الثقيلة على مؤشر ترابط منفصل ، ولن يتم إلحاق أي ضرر بتجربة المستخدم.

ماذا تلاحظ من الخارج:

  • لا يبدو أن هناك قاعدة عامة بشأن عدد العمال الذين سيتم نشرهم. يدعي بعض المطورين أن الرقم 8 هو رقم جيد ، لكن استخدم آلة حاسبة على الإنترنت وتناسبك
  • تحقق من التوافق مع المتصفحات القديمة
  • واجهت مشكلة عند تمرير الرقم 0 من الخدمة إلى العامل. لقد قمت بتطبيق .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

وهو ما يعني في الأساس أن:

  • يتم تنفيذ القرارات بشكل غير متزامن
  • لا يمكننا الاعتماد على أمر التنفيذ (أو على الأقل نحتاج إلى الثني قليلاً)
  • يتم حظر جميع الدول حتى تقوم جميع العزم بعملها ، حتى لو لم تكن مجردة.

هذا يعني أنه قبل أن يرى المستخدم أي ناتج ، يجب عليه انتظار كل التبعيات. نحن بحاجة إلى تلك البيانات ، بالتأكيد ، حسنًا. إذا كان من الضروري للغاية الحصول عليها قبل العرض ، فضعها في كتلة .run() . خلاف ذلك ، ما عليك سوى إجراء مكالمة إلى الخدمة من وحدة التحكم والتعامل مع حالة نصف التحميل بأمان. إن رؤية العمل قيد التقدم - وتم تنفيذ وحدة التحكم بالفعل ، لذا فهي تقدم بالفعل - أفضل من توقف التطبيق.

الخطأ الشائع # 9: عدم تحسين التطبيق - ثلاثة أمثلة

أ) التسبب في حدوث عدد كبير جدًا من حلقات الملخص ، مثل إرفاق أشرطة التمرير بالنماذج

هذه مشكلة عامة يمكن أن تؤدي إلى أخطاء AngularJS ، لكنني سأناقشها في مثال أشرطة التمرير. كنت أستخدم مكتبة شريط التمرير هذه ، شريط تمرير النطاق الزاوي ، لأنني كنت بحاجة إلى الوظائف الموسعة. يحتوي هذا التوجيه على النحو التالي في النسخة المصغرة:

 <body ng-controller="MainController as MC"> <div range-slider min="0" max="MC.maxPrice" pin-handle="min" model-max="MC.price" > </div> </body>

ضع في اعتبارك الكود التالي في وحدة التحكم:

 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'); } } });

لذلك هذا يعمل ببطء. سيكون الحل غير الرسمي هو تعيين مهلة على الإدخال. لكن هذا ليس مفيدًا دائمًا ، وأحيانًا لا نريد حقًا تأخير تغيير النموذج الفعلي في جميع الحالات.

لذلك سنضيف نموذجًا مؤقتًا ملزمًا بتغيير نموذج العمل عند انتهاء المهلة:

 <body ng-controller="MainController as MC"> <div range-slider min="0" max="MC.maxPrice" pin-handle="min" model-max="MC.priceTemporary" > </div> </body>

وفي وحدة التحكم:

 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); } });

ب) عدم استخدام $ applyAsync

AngularJS ليس لديها آلية استطلاع لاستدعاء $digest() . يتم تنفيذه فقط لأننا نستخدم التوجيهات (مثل ng-click ، input ) ، والخدمات ( $timeout ، $http ) ، والطرق ( $watch ) التي تقيّم الكود الخاص بنا وتستدعي ملخصًا بعد ذلك.

$digest() .$applyAsync() .

هناك طريقتان لاستخدام applyAsync الآن. طريقة آلية لطلبات $http وطريقة يدوية للباقي.

لجعل جميع طلبات http التي تعود في نفس الوقت تقريبًا يتم حلها في ملخص واحد ، قم بما يلي:

 mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });

توضح الطريقة اليدوية كيف تعمل بالفعل. ضع في اعتبارك بعض الوظائف التي يتم تشغيلها على رد الاتصال إلى مستمع أحداث Vanilla JS أو jQuery .click() ، أو بعض المكتبات الخارجية الأخرى. بعد تنفيذ النماذج وتغييرها ، إذا لم تقم بالفعل بلفها في $apply() ، فستحتاج إلى استدعاء $scope.$root.$digest() ( $rootScope.$digest() ) ، أو على الأقل $scope.$digest() range $scope.$digest() . خلاف ذلك ، لن ترى أي تغيير.

إذا قمت بذلك عدة مرات في تدفق واحد ، فقد يبدأ في العمل ببطء. ضع في اعتبارك استدعاء $scope.$applyAsync() على التعبيرات بدلاً من ذلك. سيتم تعيين استدعاء دورة واحدة فقط لكل منهم.

ج) القيام بمعالجة مكثفة للصور

إذا واجهت أداءً سيئًا ، فيمكنك التحقق من السبب باستخدام المخطط الزمني من أدوات مطوري Chrome. سأكتب المزيد عن هذه الأداة في الخطأ رقم 17. إذا كان الرسم البياني للخط الزمني الخاص بك مسيطرًا على اللون الأخضر بعد التسجيل ، فقد تكون مشكلات الأداء متعلقة بمعالجة الصور. لا يرتبط هذا بشكل صارم بـ AngularJS ، ولكن قد يحدث فوق مشكلات أداء AngularJS (والتي ستكون في الغالب باللون الأصفر على الرسم البياني). بصفتنا مهندسي الواجهة الأمامية ، نحتاج إلى التفكير في المشروع النهائي بالكامل.

خذ لحظة لتقييم:

  • هل تستخدم المنظر؟
  • هل لديك عدة طبقات من المحتوى متداخلة مع بعضها البعض؟
  • هل تحرك صورك؟
  • هل تقوم بقياس الصور (على سبيل المثال بحجم الخلفية)؟
  • هل تقوم بتغيير حجم الصور في حلقات ، وربما تتسبب في حدوث حلقات ملخصة عند تغيير الحجم؟

إذا أجبت بـ "نعم" على ثلاثة مما سبق على الأقل ، ففكر في تخفيفه. ربما يمكنك عرض أحجام مختلفة للصور وعدم تغيير حجمها على الإطلاق. ربما يمكنك إضافة "transform: translateZ (0)" إجبار اختراق معالجة GPU. أو استخدم requestAnimationFrame للمعالجات.

الخطأ الشائع العاشر: jQuerying It - شجرة DOM منفصلة

ربما تسمع في كثير من الأحيان أنه لا يوصى باستخدام jQuery مع AngularJS ، وأنه يجب تجنبه. من الضروري فهم السبب وراء هذه التصريحات. هناك ثلاثة أسباب على الأقل ، بقدر ما أستطيع أن أرى ، لكن أيا منها لا يمثل حاصرات فعلية.

السبب 1: عند تنفيذ كود jQuery ، تحتاج إلى استدعاء $digest() بنفسك. بالنسبة للعديد من الحالات ، يوجد حل AngularJS مصمم خصيصًا لـ AngularJS ويمكن أن يكون ذا استخدام أفضل داخل Angular من jQuery (على سبيل المثال ، ng-click أو نظام الأحداث).

السبب الثاني: طريقة التفكير في بناء التطبيق. إذا كنت تضيف JavaScript إلى مواقع الويب ، والتي تعيد التحميل عند التنقل ، فلا داعي للقلق بشأن استهلاك الذاكرة كثيرًا. مع تطبيقات الصفحة الواحدة ، لا داعي للقلق. إذا لم تقم بالتنظيف ، فقد يواجه المستخدمون الذين يقضون أكثر من بضع دقائق على تطبيقك مشكلات متزايدة في الأداء.

السبب 3: التنظيف ليس في الواقع أسهل شيء يمكن القيام به وتحليله. لا توجد طريقة لاستدعاء جامع البيانات المهملة من البرنامج النصي (في المتصفح). قد ينتهي بك الأمر مع أشجار DOM منفصلة. لقد أنشأت مثالًا (تم تحميل jQuery في index.html):

 <section> <test-for-toptal></test-for-toptal> <button ng-click="MC.removeDirective()">remove directive</button> </section>
 function MainController($rootScope, $scope) { this.removeDirective = function () { $rootScope.$emit('destroyDirective'); }; } function testForToptal($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: '<div><p>I AM DIRECTIVE</p></div>' }; } angular.module('app', []) .controller('MainController', MainController) .directive('testForToptal', testForToptal);

هذا توجيه بسيط ينتج عنه بعض النصوص. يوجد زر أسفله ، والذي سيؤدي فقط إلى تدمير التوجيه يدويًا.

لذلك عند إزالة التوجيه ، يبقى هناك مرجع إلى شجرة DOM في النطاق .toBeDetached. في أدوات chrome dev ، إذا قمت بالوصول إلى علامة التبويب "ملفات التعريف" ثم "أخذ لقطة كومة" ، فسترى في الإخراج:

يمكنك العيش مع القليل ، لكن هذا سيء إذا كان لديك طن. خاصة إذا لسبب ما ، كما في المثال ، قمت بتخزينه في النطاق. سيتم تقييم DOM بالكامل في كل ملخص. شجرة DOM المنفصلة المسببة للمشاكل هي تلك التي تحتوي على 4 عقد. فكيف يمكن حل هذا؟

 scope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });

تمت إزالة شجرة DOM المنفصلة التي تحتوي على 4 إدخالات!

في هذا المثال ، يستخدم التوجيه نفس النطاق ويخزن عنصر DOM في النطاق. كان من الأسهل بالنسبة لي أن أوضح ذلك بهذه الطريقة. لا يكون الأمر دائمًا بهذا السوء ، حيث يمكنك تخزينه في متغير. ومع ذلك ، سيستغرق الأمر الذاكرة إذا استمر أي إغلاق أشار إلى هذا المتغير أو أي متغير آخر من نفس نطاق الوظيفة.

الخطأ الشائع # 11: الإفراط في استخدام النطاق المعزول

عندما تحتاج إلى توجيه تعرف أنه سيتم استخدامه في مكان واحد ، أو لا تتوقع أن يتعارض مع أي بيئة يتم استخدامها فيها ، فلا داعي لاستخدام نطاق معزول. في الآونة الأخيرة ، هناك اتجاه لإنشاء مكونات قابلة لإعادة الاستخدام ، ولكن هل تعلم أن التوجيهات الزاوية الأساسية لا تستخدم نطاقًا معزولًا على الإطلاق؟

هناك سببان رئيسيان: لا يمكنك تطبيق توجيهين منفصلين للنطاق على عنصر ، وقد تواجه مشكلات في التداخل / التوريث / معالجة الحدث. خاصة فيما يتعلق بالترجمة - قد لا تكون التأثيرات كما تتوقع.

لذلك قد يفشل هذا:

 <p isolated-scope-directive another-isolated-scope-directive ng-if="MC.quux" ng-repeat="q in MC.quux"></p>

وحتى إذا كنت تستخدم توجيهًا واحدًا فقط ، فستلاحظ أنه لن تكون نماذج النطاق المعزولة ولا الأحداث التي يتم بثها في منعزل في "منعزول سكوب دايركتيف" متاحًا لـ "OtherController". لكونك حزينًا ، يمكنك الثني واستخدام سحر التضمين لجعله يعمل - ولكن في معظم حالات الاستخدام ، ليست هناك حاجة للعزل.

 <p isolated-scope-directive ng-if="MC.quux" ng-repeat="q in MC.quux"> <div ng-controller="AnotherController"> … the isolated scope is not available here, look: {{ isolatedModel }} </div> </p>

إذن ، سؤالان الآن:

  1. كيف يمكنك معالجة نماذج النطاق الأصل في توجيه من نفس النطاق؟
  2. كيف يمكنك إنشاء مثيل لقيم النموذج الجديد؟

هناك طريقتان ، في كل منهما تقوم بتمرير القيم إلى السمات. ضع في اعتبارك هذا المتحكم الرئيسي:

 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]; }

يتحكم في هذا العرض:

 <body ng-controller="MainController as MC"> <div class="cyan-surface"> <h1>Attributes test</h1> <test-directive watch-attribute="MC.foo" observe-attribute="current index: {{ MC.baz }}"></test-directive> </div> </body>

لاحظ أن "سمة المشاهدة" ليست محرفة. كل شيء يعمل ، بسبب سحر JS. هنا تعريف التوجيه:

 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' }; }

لاحظ أنه يتم تمرير attrs.watchAttribute إلى scope.$watch() بدون علامات الاقتباس! هذا يعني أن ما تم تمريره بالفعل إلى $ watch كان السلسلة MC.foo ! ومع ذلك ، فإنه يعمل ، لأن أي سلسلة تم تمريرها إلى $watch() يتم تقييمها مقابل النطاق ويكون MC.foo متاحًا في النطاق. هذه أيضًا هي الطريقة الأكثر شيوعًا لمشاهدة السمات في توجيهات AngularJS الأساسية.

شاهد الكود على جيثب للقالب ، وانظر إلى $parse و $eval لمزيد من الروعة.

الخطأ الشائع رقم 12: عدم التنظيف بعد نفسك - المراقبون ، الفترات الزمنية ، المهلات والمتغيرات

تقوم AngularJS ببعض الأعمال نيابة عنك ، ولكن ليس كلها. يجب تنظيف ما يلي يدويًا:

  • أي مراقبين غير مرتبطين بالنطاق الحالي (على سبيل المثال ، منضمون إلى $ rootScope)
  • فترات
  • المهلات
  • المتغيرات التي تشير إلى DOM في التوجيهات
  • ملحقات Dodgy jQuery ، على سبيل المثال تلك التي ليس لديها معالجات تتفاعل مع حدث JavaScript $destroy

إذا لم تفعل ذلك يدويًا ، فستواجه سلوكًا غير متوقع وتسريبات للذاكرة. والأسوأ من ذلك - أنها لن تكون مرئية على الفور ، لكنها ستزحف في النهاية. قانون مورفي.

بشكل مثير للدهشة ، يوفر AngularJS طرقًا مفيدة للتعامل مع كل هؤلاء:

 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. }); };

لاحظ الحدث jQuery $destroy destruction. يطلق عليه مثل AngularJS ، ولكن يتم التعامل معه بشكل منفصل. لن يتفاعل مراقبو Scope $ مع حدث jQuery.

الخطأ الشائع الثالث عشر: الاحتفاظ بعدد كبير جدًا من المراقبين

يجب أن يكون هذا بسيطًا جدًا الآن. هناك شيء واحد يجب فهمه هنا: $digest() . لكل {{ model }} ربط ، يقوم AngularJS بإنشاء مراقب. في كل مرحلة من مراحل الملخص ، يتم تقييم كل ارتباط من هذا القبيل ومقارنته بالقيمة السابقة. هذا يسمى الفحص القذر ، وهذا ما يفعله $ Digest. إذا تغيرت القيمة منذ آخر فحص ، فسيتم تشغيل رد اتصال المراقب. If that watcher callback modifies a model ($scope variable), a new $digest cycle is fired (up to a maximum of 10) when an exception is thrown.

Browsers don't have problems even with thousands of bindings, unless the expressions are complex. The common answer for “how many watchers are ok to have” is 2000.

So, how can we limit the number of watchers? By not watching scope models when we don't expect them to change. It is fairly easy onwards from AngularJS 1.3, since one-time bindings are in core now.

 <li ng-repeat="item in ::vastArray">{{ ::item.velocity }}</li>

After vastArray and item.velocity are evaluated once, they will never change again. You can still apply filters to the array, they will work just fine. It is just that the array itself will not be evaluated. In many cases, that is a win.

Common Mistake #14: Misunderstanding The Digest

This AngularJS error was already partly covered in mistakes 9.b and in 13. This is a more thorough explanation. AngularJS updates DOM as a result of callback functions to watchers. Every binding, that is the directive {{ someModel }} sets up watchers, but watchers are also set for many other directives like ng-if and ng-repeat . Just take a look at the source code, it is very readable. Watchers can also be set manually, and you have probably done that at least a few times yourself.

$watch() ers are bound to scopes. $Watchers can take strings, which are evaluated against the scope that the $watch() was bound to. They can also evaluate functions. And they also take callbacks. So, when $rootScope.$digest() is called, all the registered models (that is $scope variables) are evaluated and compared against their previous values. If the values don't match, the callback to the $watch() is executed.

It is important to understand that even though a model's value was changed, the callback does not fire until the next digest phase. It is called a “phase” for a reason - it can consist of several digest cycles. If only a watcher changes a scope model, another digest cycle is executed.

But $digest() is not polled for . It is called from core directives, services, methods, etc. If you change a model from a custom function that does not call .$apply , .$applyAsync , .$evalAsync , or anything else that eventually calls $digest() , the bindings will not be updated.

By the way, the source code for $digest() is actually quite complex. It is nevertheless worth reading, as the hilarious warnings make up for it.

Common Mistake #15: Not Relying On Automation, Or Relying On It Too Much

If you follow the trends within front end development and are a bit lazy - like me - then you probably try to not do everything by hand. Keeping track of all your dependencies, processing sets of files in different ways, reloading the browser after every file save - there is a lot more to developing than just coding.

So you may be using bower, and maybe npm depending on how you serve your app. There is a chance that you may be using grunt, gulp, or brunch. Or bash, which also is cool. In fact, you may have started your latest project with some Yeoman generator!

This leads to the question: do you understand the whole process of what your infrastructure really does? Do you need what you have, especially if you just spent hours trying to fix your connect webserver livereload functionality?

Take a second to assess what you need. All those tools are only here to aid you, there is no other reward for using them. The more experienced developers I talk to tend to simplify things.

Common Mistake #16: Not Running The Unit Tests In TDD Mode

Tests will not make your code free of AngularJS error messages. What they will do is assure that your team doesn't run into regression issues all the time.

I am writing specifically about unit tests here, not because I feel they are more important than e2e tests, but because they execute much faster. I must admit that the process I am about to describe is a very pleasurable one.

Test Driven Development as an implementation for eg gulp-karma runner, basically runs all your unit tests on every file save. My favorite way to write tests is, I just write empty assurances first:

 describe('some module', function () { it('should call the name-it service…', function () { // leave this empty for now }); ... });

After that, I write or refactor the actual code, then I come back to the tests and fill in the assurances with actual test code.

Having a TDD task running in a terminal speeds up the process by about 100%. Unit tests execute in a matter of a few seconds, even if you have a lot of them. Just save the test file and the runner will pick it up, evaluate your tests, and provide feedback instantly.

With e2e tests, the process is much slower. My advice - split e2e tests up into test suites and just run one at a time. Protractor has support for them, and below is the code I use for my test tasks (I like gulp).

 '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);

Common Mistake #17: Not Using The Available Tools

A - chrome breakpoints

Chrome dev tools allow you to point at a specific place in any of the files loaded into the browser, pause code execution at that point, and let you interact with all the variables available from that point. That is a lot! That functionality does not require you to add any code at all, everything happens in the dev tools.

Not only you get access to all the variables, you also see the call stack, print stack traces, and more. You can even configure it to work with minified files. Read about it here.

There are other ways you can get similar run-time access, eg by adding console.log() calls. But breakpoints are more sophisticated.

AngularJS also allows you to access scope through DOM elements (as long as debugInfo is enabled), and inject available services through the console. Consider the following in the console:

 $(document.body).scope().$root

or point at an element in the inspector, and then:

 $($0).scope()

Even if debugInfo is not enabled, you can do:

 angular.reloadWithDebugInfo()

And have it available after reload:

To inject and interact with a service from the console, try:

 var injector = $(document.body).injector(); var someService = injector.get('someService');

B - chrome timeline

Another great tool that comes with dev tools is the timeline. That will allow you to record and analyse your app's live performance as you are using it. The output shows, among others, memory usage, frame rate, and the dissection of the different processes that occupy the CPU: loading, scripting, rendering, and painting.

If you experience that your app's performance degrades, you will most likely be able to find the cause for that through the timeline tab. Just record your actions which led to performance issues and see what happens. Too many watchers? You will see yellow bars taking a lot of space. Memory leaks? You can see how much memory was consumed over time on a graph.

A detailed description: https://developer.chrome.com/devtools/docs/timeline

C - inspecting apps remotely on iOS and Android

If you are developing a hybrid app or a responsive web app, you can access your device's console, DOM tree, and all other tools available either through Chrome or Safari dev tools. That includes the WebView and UIWebView.

أولاً ، ابدأ خادم الويب الخاص بك على المضيف 0.0.0.0 بحيث يمكن الوصول إليه من شبكتك المحلية. تمكين مراقب الويب في الإعدادات. ثم قم بتوصيل جهازك بسطح المكتب الخاص بك والوصول إلى صفحة التطوير المحلية الخاصة بك ، باستخدام عنوان IP الخاص بجهازك بدلاً من "المضيف المحلي" العادي. هذا كل ما يتطلبه الأمر ، يجب أن يكون جهازك متاحًا لك الآن من متصفح سطح المكتب.

فيما يلي الإرشادات التفصيلية لنظامي Android و iOS ، يمكن العثور على أدلة غير رسمية بسهولة من خلال google.

لقد حصلت مؤخرًا على تجربة رائعة مع browserSync. إنه يعمل بطريقة مشابهة لـ تحميل كبد ، ولكنه في الواقع يقوم أيضًا بمزامنة جميع المتصفحات التي تعرض نفس الصفحة من خلال browserSync. يتضمن ذلك تفاعل المستخدم مثل التمرير والنقر على الأزرار وما إلى ذلك. كنت أنظر إلى مخرجات سجل تطبيق iOS أثناء التحكم في الصفحة على iPad من سطح المكتب. عملت بشكل جيد!

الخطأ الشائع 18: عدم قراءة كود المصدر في مثال NG-INIT

يجب أن يكون Ng-init ، من صوته ، مشابهًا لـ ng-if و ng-repeat ، أليس كذلك؟ هل تساءلت يومًا عن سبب وجود تعليق في المستندات بعدم استخدامه؟ IMHO كان ذلك مفاجئًا! أتوقع أن يقوم التوجيه بتهيئة نموذج. هذا أيضًا ما يفعله ، لكن ... يتم تنفيذه بطريقة مختلفة ، أي أنه لا يراقب قيمة السمة. لست بحاجة إلى تصفح شفرة مصدر AngularJS - دعني أحضرها لك:

 var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });

أقل مما تتوقع؟ يمكن قراءته تمامًا ، إلى جانب بناء جملة التوجيه المحرج ، أليس كذلك؟ السطر السادس هو ما يدور حوله كل شيء.

قارنها بـ ng-show:

 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 هناك ، وهذا ما يجعل هذا التوجيه ديناميكيًا. في الكود المصدري AngularJS ، جزء كبير من كل الكود عبارة عن تعليقات تصف الكود الذي كان يمكن قراءته في الغالب من البداية. أعتقد أنها طريقة رائعة للتعرف على AngularJS.

خاتمة

هذا الدليل الذي يغطي أخطاء AngularJS الأكثر شيوعًا يبلغ ضعف طول الأدلة الأخرى تقريبًا. اتضح بهذه الطريقة بشكل طبيعي. الطلب على مهندسي الواجهة الأمامية لجافا سكريبت عالي الجودة مرتفع للغاية. AngularJS حار جدًا في الوقت الحالي ، وقد احتل موقعًا مستقرًا بين أدوات التطوير الأكثر شيوعًا لبضع سنوات. مع AngularJS 2.0 على الطريق ، فمن المحتمل أن يهيمن لسنوات قادمة.

إن الشيء العظيم في تطوير الواجهة الأمامية هو أنها مجزية للغاية. يظهر عملنا على الفور ويتفاعل الأشخاص مباشرةً مع المنتجات التي نقدمها. الوقت الذي نقضيه في تعلم JavaScript ، وأعتقد أننا يجب أن نركز على لغة JavaScript ، يعد استثمارًا جيدًا للغاية. إنها لغة الإنترنت. المنافسة قوية للغاية! هناك تركيز واحد بالنسبة لنا - تجربة المستخدم. لكي نكون ناجحين ، نحتاج إلى تغطية كل شيء.

يمكن تنزيل كود المصدر المستخدم في هذه الأمثلة من GitHub. لا تتردد في تنزيله وجعله خاصا بك.

كنت أرغب في منح اعتمادات لأربعة من مطوري النشر الذين ألهموني أكثر:

  • بن نادل
  • تود شعار
  • باسكال بريشت
  • سانديب باندا

أردت أيضًا أن أشكر جميع الأشخاص الرائعين على قنوات FreeNode #angularjs و # javascript على العديد من المحادثات الممتازة ، والدعم المستمر.

وأخيرًا ، تذكر دائمًا:

 // when in doubt, comment it out! :)