Приложенията за една страница изискват от разработчиците от предния край да станат по-добри софтуерни инженери. CSS и HTML вече не са най-голямата грижа, всъщност вече няма само една грижа. Предният разработчик трябва да обработва XHRs, логика на приложението (модели, изгледи, контролери), производителност, анимации, стилове, структура, SEO и интеграция с външни услуги. Резултатът, който се получава от всички тези комбинирани, е потребителският опит (UX), който винаги трябва да бъде приоритет.
AngularJS е много мощна рамка. Това е третото хранилище с най-много звезди в GitHub. Не е трудно да започнете да използвате, но целите, които е предназначен за постигане на разбиране на търсенето. Вече не могат разработчиците на AngularJS да пренебрегват консумацията на памет, защото вече няма да се нулират при навигация. Това е авангардът на уеб разработка . Нека го прегърнем!
Има няколко оптимизации за оптимизация, препоръчани за производство. Един от тях е деактивиране на информация за отстраняване на грешки.
DebugInfoEnabled
е настройка, която по подразбиране е истина и позволява достъп до обхват чрез DOM възли. Ако искате да опитате това чрез JavaScript конзолата, изберете елемент DOM и влезте в неговия обхват с:
angular.element(document.body).scope()
Той може да бъде полезен дори когато не се използва jQuery със своя CSS, но не трябва да се използва извън конзолата. Причината е, че когато $compileProvider.debugInfoEnabled
е зададено на false, извикващо .scope()
на DOM възел ще се върне undefined
.
Това е една от малкото препоръчителни опции за производство.
Моля, обърнете внимание, че все още можете да получите достъп до обхвата през конзолата, дори когато е в производство. Обадете се 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
Щракването върху бутона с надпис „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() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
Това е много по-малко объркващо. Особено когато има много вложени обхвати, какъвто може да бъде случаят с вложени състояния.
В синтаксиса на контролера има още нещо.
Има няколко предупреждения за това как е изложен обектът на контролера. По същество това е обект, зададен в обхвата на контролера, точно като нормален модел.
Ако трябва да наблюдавате свойство на обекта на контролера, можете да гледате функция, но не се изисква. Ето пример:
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'; } }
За да можете обаче да го направите, трябва да сте съобразени със съкращението, което използвате за controllerAs. Има поне три начина да го зададете. Вече видяхте първата:
…
Ако обаче използвате 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.
Мислете за това като за сложен, но стандартен случай на употреба. Има приложение, което има изглед на начална страница и изглед на продукта. Изгледът на продукта има три отделни раздела: въведение, приспособление и съдържание. Искаме приспособлението да продължи и да не се презарежда при превключване между състоянието. Но съдържанието трябва да се презареди.
Обмислете следната структура на страницата с индекс на HTML продукти:
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
Това е нещо, което бихме могли да получим от HTML кодера и сега трябва да го разделим на файлове и състояния. Обикновено приемам конвенцията, че има абстрактно ГЛАВНО състояние, което съхранява глобалните данни, ако е необходимо. Използвайте това вместо $ rootScope. The Основна state ще запази и статичния 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
Както можете да видите, страницата на продуктовия индекс има три изгледа с име. Един за въведение, един за приспособлението и един за продукта. Ние отговаряме на спецификациите! Така че сега нека настроим маршрутизация:
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()
? Съдържанието и приспособлението се презареждат, но ние искаме само да презаредим съдържанието. Това беше проблематично и разработчиците всъщност създадоха рутери, които да поддържат точно тази функционалност. Едно от имената за това беше лепкави гледки . За щастие, '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; }
, но мисля, че използването на конфигурация на състоянието всъщност е по-добро. Ако се интересувате от прихващане на маршрути, написах малък урок за StackOverflow .
Това грешка е с по-лек калибър и е по-скоро въпрос на стил, отколкото избягване на съобщенията за грешка AngularJS. Може би преди сте забелязали, че рядко предавам анонимни функции на ъглови вътрешни декларации. Обикновено просто първо определям функция и след това я предавам.
Това се отнася не само до функциите. Получих този подход от четене на ръководства за стил, особено на Airbnb и Todd Motto. Вярвам, че има няколко предимства и почти никакви недостатъци.
На първо място, можете много по-лесно да манипулирате и мутирате вашите функции и обекти, ако те са присвоени на променлива. На второ място, кодът е по-чист и може лесно да бъде разделен на файлове. Това означава поддържаемост. Ако не искате да замърсявате глобалното пространство от имена, обвийте всеки файл в IIFE. Третата причина е проверимостта. Помислете за този пример:
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()
Тук не става въпрос само за стил, тъй като всъщност кодът е по-многократно използван и идиоматичен. Разработчикът получава по-изразителна сила. Разделянето на целия код на самостоятелни блокове просто го улеснява.
В някои сценарии може да се наложи да се обработи голям набор от сложни обекти, като се преминат през набор от филтри, декоратори и накрая алгоритъм за сортиране. Един от случаите на употреба е, когато приложението трябва да работи офлайн или когато ефективността на показване на данни е ключова. И тъй като JavaScript е с една нишка, сравнително лесно е да замразите браузъра.
Също така е лесно да го избегнете с уеб работници. Изглежда няма популярни библиотеки, които да се справят с това специално за 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
както всеки метод за услуга, който връща обещание. Тежката обработка ще се извърши на отделна нишка и няма да се навреди на UX.
Какво да внимавате:
.run()
върху предадения имот и той е работил правилно.Решенията добавят допълнително време към зареждането на изгледа. Вярвам, че високата производителност на front-end приложението е нашата основна цел. Не би трябвало да представлява проблем да изобразите някои части от изгледа, докато приложението чака данните от 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()
блок. В противен случай просто се обадете на услугата от контролера и обработете изящно полузареденото състояние. Виждането на текущата работа - и контролерът вече е изпълнен, така че всъщност е напредък - е по-добре, отколкото да спрете приложението.
а) Причиняване на твърде много цикли за усвояване, като например прикрепване на плъзгачи към модели
Това е общ проблем, който може да доведе до грешки в AngularJS, но ще го обсъдя на примера на плъзгачи. Използвах тази библиотека с плъзгачи, плъзгач с ъглови диапазони, защото имах нужда от разширената функционалност. Тази директива има този синтаксис в минималната версия:
ng-click
Помислете за следния код в контролера:
input
Така че това работи бавно. Случайното решение би било да зададете таймаут на входа. Но това не винаги е удобно и понякога наистина не искаме да отлагаме реалната промяна на модела във всички случаи.
Така че ще добавим временен модел, обвързан с промяна на работния модел при изчакване:
$timeout
и в контролера:
$http
б) Не се използва $ applyAsync
AngularJS няма механизъм за извикване за извикване $watch
. Изпълнява се само защото използваме директивите (напр. .$applyAsync()
, $digest()
), Услуги (applyAsync
, $http
) и методи (mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
), които оценяват нашия код и извикайте дайджест след това.
Какво .click()
прави е, че забавя разделителната способност на изразите до следващото $apply()
цикъл, който се задейства след 0 таймаут, което всъщност е ~ 10ms.
Има два начина за използване $scope.$root.$digest()
сега. Автоматизиран начин за $rootScope.$digest()
заявки и ръчен начин за останалите.
За да направите всички http заявки, които се връщат по едно и също време, да бъдат разрешени в един дайджест, направете:
$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(); });
. В противен случай няма да видите промяна.
Ако го направите няколко пъти в един поток, той може да започне да работи бавно. Помислете за извикване … the isolated scope is not available here, look: {{ isolatedModel }}
вместо това върху изразите. Той ще зададе само един цикъл на усвояване за всички тях.
в) Извършване на тежка обработка на изображения
Ако имате лоша производителност, можете да проучите причината, като използвате хронологията от Chrome Developer Tools. Ще пиша повече за този инструмент в грешка # 17. Ако вашата графика на времевата линия е доминирана със зеления цвят след запис, вашите проблеми с производителността може да са свързани с обработката на изображения. Това не е строго свързано с AngularJS, но може да се случи върху проблеми с производителността на AngularJS (които биха били предимно жълти на графиката). Като предни инженери, трябва да помислим за цялостния краен проект.
Отделете малко време, за да прецените:
Ако сте отговорили с „да“ на поне три от горепосочените, помислете за облекчаването му. Може би можете да обслужвате различни размери на изображенията и изобщо да не преоразмерявате. Може би бихте могли да добавите 'преобразуване: translateZ (0)' принудителна обработка на графичен процесор. Или използвайте requestAnimationFrame за манипулатори.
Много пъти вероятно чувате, че не се препоръчва да използвате jQuery с AngularJS и че трябва да се избягва. Наложително е да се разбере причината зад тези твърдения. Има поне три причини, доколкото виждам, но нито една от тях не е действителна блокери.
Причина 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 и може да бъде по-полезен в Angular от jQuery (напр. ng-click или системата за събития).
Причина 2: Методът на мислене за изграждане на приложението. Ако сте добавяли JavaScript към уебсайтове, които се презареждат при навигация, не е трябвало да се притеснявате от твърде голяма консумация на памет. С приложенията на една страница трябва да се притеснявате. Ако не почистите, потребителите, които прекарват повече от няколко минути в приложението ви, може да срещнат нарастващи проблеми с производителността.
Причина 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' }; }
Това е проста директива, която извежда малко текст. Под него има бутон, който просто ще унищожи директивата ръчно.
Така че, когато директивата бъде премахната, остава препратка към DOM дървото в scope.toBeDetached. В инструментите на chrome dev, ако отворите раздела „Профили“ и след това „направите купчина моментна снимка“, ще видите в резултата:
Можете да живеете с няколко, но е лошо, ако имате тон. Особено ако по някаква причина, като в примера, го съхранявате в обхвата. Целият DOM ще бъде оценен при всеки сборник. Проблемното отделено DOM дърво е това с 4 възела. И така, как може да се реши това?
attrs.watchAttribute
Откъснатото DOM дърво с 4 записа е премахнато!
В този пример директивата използва същия обхват и съхранява елемента DOM в обхвата. За мен беше по-лесно да го демонстрирам по този начин. Не винаги става толкова зле, тъй като можете да го съхраните в променлива. Въпреки това, тя все още ще заема памет, ако някое затваряне, което е препращало към тази променлива или някоя друга от същия обхват на функцията, живее.
Винаги, когато имате нужда от директива, за която знаете, че ще бъде използвана на едно място или която не очаквате да противоречи на каквато и среда да се използва, няма нужда да използвате изолиран обхват. Напоследък има тенденция да се създават компоненти за многократна употреба, но знаете ли, че основните ъглови директиви изобщо не използват изолиран обхват?
Има две основни причини: не можете да приложите две изолирани директиви за обхват към елемент и може да срещнете проблеми с влагане / наследяване / обработка на събитие. Особено по отношение на включването - ефектите може да не са такива, каквито очаквате.
Така че това ще се провали:
scope.$watch()
И дори ако използвате само една директива, ще забележите, че нито изолираните модели на обхват, нито събитията, излъчвани в isolatedScopeDirective, няма да бъдат достъпни за AnotherController. Тъжно е, че можете да се огънете и да използвате магия за включване, за да го накарате да работи - но за повечето случаи на употреба няма нужда да се изолирате.
MC.foo
И така, два въпроса сега:
Има два начина, при които и двамата предавате стойности на атрибути. Помислете за този MainController:
$watch()
Това контролира този изглед:
MC.foo
Забележете, че „атрибут на часовник“ не е интерполиран. Всичко работи, благодарение на 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, но се обработва отделно. Наблюдателите на Scope $ няма да реагират на събитието jQuery.
Сега това трябва да е съвсем просто. Тук трябва да се разбере едно нещо: ng-if
. За всяко подвързване ng-repeat
, AngularJS създава наблюдател. Във всяка фаза на усвояване, всяко такова свързване се оценява и сравнява с предишната стойност. Това се нарича мръсна проверка и това прави $ digest. Ако стойността се промени след последната проверка, извикването на наблюдателя се задейства. Ако този обратен извикващ наблюдател модифицира модел (променлива $ scope), се задейства нов цикъл $ digest (до максимум 10), когато се появи изключение.
Браузърите нямат проблеми дори с хиляди обвързвания, освен ако изразите не са сложни. Общият отговор за „колко наблюдатели са добре да има“ е 2000.
И така, как можем да ограничим броя на наблюдателите? Като не гледаме модели на обхват, когато не очакваме да се променят. От AngularJS 1.3 е доста лесно, тъй като еднократните обвързвания вече са в основата.
$watch()
След $Watchers
и $watch()
се оценяват веднъж, никога повече няма да се променят. Все още можете да приложите филтри към масива, те ще работят добре. Просто самият масив няма да бъде оценен. В много случаи това е победа.
Тази грешка в AngularJS вече беше частично покрита в грешки 9.b и в 13. Това е по-задълбочено обяснение. AngularJS актуализира DOM в резултат на функции за обратно извикване на наблюдатели. Всяко обвързване, това е директивата $rootScope.$digest()
настройва наблюдатели, но наблюдателите са настроени и за много други директиви като $scope
и $watch()
. Просто погледнете изходния код, той е много четим. Наблюдателите могат да се настройват и ръчно и вероятно сте го правили поне няколко пъти сами.
$digest()
своите са обвързани с обхват. .$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
, обвързването няма да се актуализира.
Между другото, изходният код за $(
Топ 18 най-често срещани грешки, които създават разработчиците на AngularJS
Приложенията за една страница изискват от разработчиците от предния край да станат по-добри софтуерни инженери. CSS и HTML вече не са най-голямата грижа, всъщност вече няма само една грижа. Предният разработчик трябва да обработва XHRs, логика на приложението (модели, изгледи, контролери), производителност, анимации, стилове, структура, SEO и интеграция с външни услуги. Резултатът, който се получава от всички тези комбинирани, е потребителският опит (UX), който винаги трябва да бъде приоритет.
AngularJS е много мощна рамка. Това е третото хранилище с най-много звезди в GitHub. Не е трудно да започнете да използвате, но целите, които е предназначен за постигане на разбиране на търсенето. Вече не могат разработчиците на AngularJS да пренебрегват консумацията на памет, защото вече няма да се нулират при навигация. Това е авангардът на уеб разработка . Нека го прегърнем!
Има няколко оптимизации за оптимизация, препоръчани за производство. Един от тях е деактивиране на информация за отстраняване на грешки.
DebugInfoEnabled
е настройка, която по подразбиране е истина и позволява достъп до обхват чрез DOM възли. Ако искате да опитате това чрез JavaScript конзолата, изберете елемент DOM и влезте в неговия обхват с:
angular.element(document.body).scope()
Той може да бъде полезен дори когато не се използва jQuery със своя CSS, но не трябва да се използва извън конзолата. Причината е, че когато $compileProvider.debugInfoEnabled
е зададено на false, извикващо .scope()
на DOM възел ще се върне undefined
.
Това е една от малкото препоръчителни опции за производство.
Моля, обърнете внимание, че все още можете да получите достъп до обхвата през конзолата, дори когато е в производство. Обадете се 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
Щракването върху бутона с надпис „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() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
Това е много по-малко объркващо. Особено когато има много вложени обхвати, какъвто може да бъде случаят с вложени състояния.
В синтаксиса на контролера има още нещо.
Има няколко предупреждения за това как е изложен обектът на контролера. По същество това е обект, зададен в обхвата на контролера, точно като нормален модел.
Ако трябва да наблюдавате свойство на обекта на контролера, можете да гледате функция, но не се изисква. Ето пример:
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'; } }
За да можете обаче да го направите, трябва да сте съобразени със съкращението, което използвате за controllerAs. Има поне три начина да го зададете. Вече видяхте първата:
…
Ако обаче използвате 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.
Мислете за това като за сложен, но стандартен случай на употреба. Има приложение, което има изглед на начална страница и изглед на продукта. Изгледът на продукта има три отделни раздела: въведение, приспособление и съдържание. Искаме приспособлението да продължи и да не се презарежда при превключване между състоянието. Но съдържанието трябва да се презареди.
Обмислете следната структура на страницата с индекс на HTML продукти:
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
Това е нещо, което бихме могли да получим от HTML кодера и сега трябва да го разделим на файлове и състояния. Обикновено приемам конвенцията, че има абстрактно ГЛАВНО състояние, което съхранява глобалните данни, ако е необходимо. Използвайте това вместо $ rootScope. The Основна state ще запази и статичния 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
Както можете да видите, страницата на продуктовия индекс има три изгледа с име. Един за въведение, един за приспособлението и един за продукта. Ние отговаряме на спецификациите! Така че сега нека настроим маршрутизация:
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()
? Съдържанието и приспособлението се презареждат, но ние искаме само да презаредим съдържанието. Това беше проблематично и разработчиците всъщност създадоха рутери, които да поддържат точно тази функционалност. Едно от имената за това беше лепкави гледки . За щастие, '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; }
, но мисля, че използването на конфигурация на състоянието всъщност е по-добро. Ако се интересувате от прихващане на маршрути, написах малък урок за StackOverflow .
Това грешка е с по-лек калибър и е по-скоро въпрос на стил, отколкото избягване на съобщенията за грешка AngularJS. Може би преди сте забелязали, че рядко предавам анонимни функции на ъглови вътрешни декларации. Обикновено просто първо определям функция и след това я предавам.
Това се отнася не само до функциите. Получих този подход от четене на ръководства за стил, особено на Airbnb и Todd Motto. Вярвам, че има няколко предимства и почти никакви недостатъци.
На първо място, можете много по-лесно да манипулирате и мутирате вашите функции и обекти, ако те са присвоени на променлива. На второ място, кодът е по-чист и може лесно да бъде разделен на файлове. Това означава поддържаемост. Ако не искате да замърсявате глобалното пространство от имена, обвийте всеки файл в IIFE. Третата причина е проверимостта. Помислете за този пример:
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()
Тук не става въпрос само за стил, тъй като всъщност кодът е по-многократно използван и идиоматичен. Разработчикът получава по-изразителна сила. Разделянето на целия код на самостоятелни блокове просто го улеснява.
В някои сценарии може да се наложи да се обработи голям набор от сложни обекти, като се преминат през набор от филтри, декоратори и накрая алгоритъм за сортиране. Един от случаите на употреба е, когато приложението трябва да работи офлайн или когато ефективността на показване на данни е ключова. И тъй като JavaScript е с една нишка, сравнително лесно е да замразите браузъра.
Също така е лесно да го избегнете с уеб работници. Изглежда няма популярни библиотеки, които да се справят с това специално за 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
както всеки метод за услуга, който връща обещание. Тежката обработка ще се извърши на отделна нишка и няма да се навреди на UX.
Какво да внимавате:
.run()
върху предадения имот и той е работил правилно.Решенията добавят допълнително време към зареждането на изгледа. Вярвам, че високата производителност на front-end приложението е нашата основна цел. Не би трябвало да представлява проблем да изобразите някои части от изгледа, докато приложението чака данните от 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()
блок. В противен случай просто се обадете на услугата от контролера и обработете изящно полузареденото състояние. Виждането на текущата работа - и контролерът вече е изпълнен, така че всъщност е напредък - е по-добре, отколкото да спрете приложението.
а) Причиняване на твърде много цикли за усвояване, като например прикрепване на плъзгачи към модели
Това е общ проблем, който може да доведе до грешки в AngularJS, но ще го обсъдя на примера на плъзгачи. Използвах тази библиотека с плъзгачи, плъзгач с ъглови диапазони, защото имах нужда от разширената функционалност. Тази директива има този синтаксис в минималната версия:
ng-click
Помислете за следния код в контролера:
input
Така че това работи бавно. Случайното решение би било да зададете таймаут на входа. Но това не винаги е удобно и понякога наистина не искаме да отлагаме реалната промяна на модела във всички случаи.
Така че ще добавим временен модел, обвързан с промяна на работния модел при изчакване:
$timeout
и в контролера:
$http
б) Не се използва $ applyAsync
AngularJS няма механизъм за извикване за извикване $watch
. Изпълнява се само защото използваме директивите (напр. .$applyAsync()
, $digest()
), Услуги (applyAsync
, $http
) и методи (mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
), които оценяват нашия код и извикайте дайджест след това.
Какво .click()
прави е, че забавя разделителната способност на изразите до следващото $apply()
цикъл, който се задейства след 0 таймаут, което всъщност е ~ 10ms.
Има два начина за използване $scope.$root.$digest()
сега. Автоматизиран начин за $rootScope.$digest()
заявки и ръчен начин за останалите.
За да направите всички http заявки, които се връщат по едно и също време, да бъдат разрешени в един дайджест, направете:
$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(); });
. В противен случай няма да видите промяна.
Ако го направите няколко пъти в един поток, той може да започне да работи бавно. Помислете за извикване … the isolated scope is not available here, look: {{ isolatedModel }}
вместо това върху изразите. Той ще зададе само един цикъл на усвояване за всички тях.
в) Извършване на тежка обработка на изображения
Ако имате лоша производителност, можете да проучите причината, като използвате хронологията от Chrome Developer Tools. Ще пиша повече за този инструмент в грешка # 17. Ако вашата графика на времевата линия е доминирана със зеления цвят след запис, вашите проблеми с производителността може да са свързани с обработката на изображения. Това не е строго свързано с AngularJS, но може да се случи върху проблеми с производителността на AngularJS (които биха били предимно жълти на графиката). Като предни инженери, трябва да помислим за цялостния краен проект.
Отделете малко време, за да прецените:
Ако сте отговорили с „да“ на поне три от горепосочените, помислете за облекчаването му. Може би можете да обслужвате различни размери на изображенията и изобщо да не преоразмерявате. Може би бихте могли да добавите 'преобразуване: translateZ (0)' принудителна обработка на графичен процесор. Или използвайте requestAnimationFrame за манипулатори.
Много пъти вероятно чувате, че не се препоръчва да използвате jQuery с AngularJS и че трябва да се избягва. Наложително е да се разбере причината зад тези твърдения. Има поне три причини, доколкото виждам, но нито една от тях не е действителна блокери.
Причина 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 и може да бъде по-полезен в Angular от jQuery (напр. ng-click или системата за събития).
Причина 2: Методът на мислене за изграждане на приложението. Ако сте добавяли JavaScript към уебсайтове, които се презареждат при навигация, не е трябвало да се притеснявате от твърде голяма консумация на памет. С приложенията на една страница трябва да се притеснявате. Ако не почистите, потребителите, които прекарват повече от няколко минути в приложението ви, може да срещнат нарастващи проблеми с производителността.
Причина 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' }; }
Това е проста директива, която извежда малко текст. Под него има бутон, който просто ще унищожи директивата ръчно.
Така че, когато директивата бъде премахната, остава препратка към DOM дървото в scope.toBeDetached. В инструментите на chrome dev, ако отворите раздела „Профили“ и след това „направите купчина моментна снимка“, ще видите в резултата:
Можете да живеете с няколко, но е лошо, ако имате тон. Особено ако по някаква причина, като в примера, го съхранявате в обхвата. Целият DOM ще бъде оценен при всеки сборник. Проблемното отделено DOM дърво е това с 4 възела. И така, как може да се реши това?
attrs.watchAttribute
Откъснатото DOM дърво с 4 записа е премахнато!
В този пример директивата използва същия обхват и съхранява елемента DOM в обхвата. За мен беше по-лесно да го демонстрирам по този начин. Не винаги става толкова зле, тъй като можете да го съхраните в променлива. Въпреки това, тя все още ще заема памет, ако някое затваряне, което е препращало към тази променлива или някоя друга от същия обхват на функцията, живее.
Винаги, когато имате нужда от директива, за която знаете, че ще бъде използвана на едно място или която не очаквате да противоречи на каквато и среда да се използва, няма нужда да използвате изолиран обхват. Напоследък има тенденция да се създават компоненти за многократна употреба, но знаете ли, че основните ъглови директиви изобщо не използват изолиран обхват?
Има две основни причини: не можете да приложите две изолирани директиви за обхват към елемент и може да срещнете проблеми с влагане / наследяване / обработка на събитие. Особено по отношение на включването - ефектите може да не са такива, каквито очаквате.
Така че това ще се провали:
scope.$watch()
И дори ако използвате само една директива, ще забележите, че нито изолираните модели на обхват, нито събитията, излъчвани в isolatedScopeDirective, няма да бъдат достъпни за AnotherController. Тъжно е, че можете да се огънете и да използвате магия за включване, за да го накарате да работи - но за повечето случаи на употреба няма нужда да се изолирате.
MC.foo
И така, два въпроса сега:
Има два начина, при които и двамата предавате стойности на атрибути. Помислете за този MainController:
$watch()
Това контролира този изглед:
MC.foo
Забележете, че „атрибут на часовник“ не е интерполиран. Всичко работи, благодарение на 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, но се обработва отделно. Наблюдателите на Scope $ няма да реагират на събитието jQuery.
Сега това трябва да е съвсем просто. Тук трябва да се разбере едно нещо: ng-if
. За всяко подвързване ng-repeat
, AngularJS създава наблюдател. Във всяка фаза на усвояване, всяко такова свързване се оценява и сравнява с предишната стойност. Това се нарича мръсна проверка и това прави $ digest. Ако стойността се промени след последната проверка, извикването на наблюдателя се задейства. Ако този обратен извикващ наблюдател модифицира модел (променлива $ scope), се задейства нов цикъл $ digest (до максимум 10), когато се появи изключение.
Браузърите нямат проблеми дори с хиляди обвързвания, освен ако изразите не са сложни. Общият отговор за „колко наблюдатели са добре да има“ е 2000.
И така, как можем да ограничим броя на наблюдателите? Като не гледаме модели на обхват, когато не очакваме да се променят. От AngularJS 1.3 е доста лесно, тъй като еднократните обвързвания вече са в основата.
$watch()
След $Watchers
и $watch()
се оценяват веднъж, никога повече няма да се променят. Все още можете да приложите филтри към масива, те ще работят добре. Просто самият масив няма да бъде оценен. В много случаи това е победа.
Тази грешка в AngularJS вече беше частично покрита в грешки 9.b и в 13. Това е по-задълбочено обяснение. AngularJS актуализира DOM в резултат на функции за обратно извикване на наблюдатели. Всяко обвързване, това е директивата $rootScope.$digest()
настройва наблюдатели, но наблюдателите са настроени и за много други директиви като $scope
и $watch()
. Просто погледнете изходния код, той е много четим. Наблюдателите могат да се настройват и ръчно и вероятно сте го правили поне няколко пъти сами.
$digest()
своите са обвързани с обхват. .$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 в зависимост от това как обслужвате приложението си. Има шанс да използвате мрънкане, глътка или закуска. Или баш, което също е готино. Всъщност, може би сте започнали най-новия си проект с някакъв Yeoman генератор!
Това води до въпроса: разбирате ли целия процес на това, което вашата инфраструктура наистина прави? Имате ли нужда от това, което имате, особено ако току-що сте прекарали часове, опитвайки се да поправите функцията за свързване на уеб сървър на живо?
Отделете секунда, за да прецените от какво имате нужда. Всички тези инструменти са само тук, за да ви помогнат, няма друга награда за използването им. По-опитните разработчици, с които говоря, са склонни да опростяват нещата.
Тестовете няма да направят кода ви свободен от съобщения за грешка AngularJS. Това, което те ще направят, е да гарантират, че екипът ви не се сблъсква постоянно с проблеми с регресията.
Тук пиша специално за модулни тестове, не защото смятам, че те са по-важни от e2e тестовете, а защото се изпълняват много по-бързо. Трябва да призная, че процесът, който ще опиша, е много приятен.
Test Driven Development като изпълнение за напр. gulp-karma runner, основно изпълнява всичките ви модулни тестове при всяко записване на файл. Любимият ми начин да пиша тестове е, че първо пиша празни уверения:
angular.reloadWithDebugInfo()
След това пиша или рефакторирам действителния код, след това се връщам към тестовете и попълвам уверенията с действителен тестов код.
Изпълнението на TDD задача в терминал ускорява процеса с около 100%. Единичните тестове се изпълняват за няколко секунди, дори ако имате много от тях. Просто запазете тестовия файл и бегачът ще го вземе, ще оцени вашите тестове и ще предостави незабавна обратна връзка.
При тестовете e2e процесът е много по-бавен. Моят съвет - разделете тестовете e2e на тестови пакети и просто изпълнявайте един по един. Protractor има поддръжка за тях, а по-долу е кодът, който използвам за тестовите си задачи (харесвам глътка).
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 - хромирана хронология
Друг чудесен инструмент, който идва с инструментите за разработчици, е хронологията. Това ще ви позволи да записвате и анализирате ефективността на приложението си на живо, докато го използвате. Резултатът показва, наред с други, използването на паметта, честотата на кадрите и дисекцията на различните процеси, които заемат процесора: зареждане, скриптове, изобразяване и рисуване.
Ако почувствате, че ефективността на приложението ви се влошава, най-вероятно ще можете да намерите причината за това чрез раздела с времевата линия. Просто запишете действията си, които са довели до проблеми с производителността, и вижте какво се случва. Твърде много наблюдатели? Ще видите жълти ленти, които заемат много място. Изтичане на памет? Можете да видите колко памет е изразходвано с течение на времето на графика.
Подробно описание: https://developer.chrome.com/devtools/docs/timeline
C - инспектиране на приложения от разстояние на iOS и Android
Ако разработвате хибридно приложение или адаптивно уеб приложение, можете да осъществите достъп до конзолата на вашето устройство, DOM дървото и всички други инструменти, налични чрез Chrome или Safari dev tools. Това включва WebView и UIWebView.
Първо стартирайте вашия уеб сървър на хост 0.0.0.0, така че да е достъпен от вашата локална мрежа. Активирайте уеб инспектора в настройките. След това свържете устройството си с вашия работен плот и отворете страницата си за локално развитие, като използвате ip на вашето устройство вместо обикновения „localhost“. Това е всичко, вашето устройство вече трябва да е достъпно за вас от браузъра на вашия работен плот.
Тук са подробните инструкции за Android И за iOS, неофициалните ръководства се намират лесно чрез google.
Наскоро имах страхотен опит с browserSync . Той работи по подобен начин за livereload, но всъщност също така синхронизира всички браузъри, които гледат една и съща страница чрез browserSync. Това включва взаимодействие с потребителите, като превъртане, щракване върху бутони и др. Разглеждах изходните данни на приложението за iOS, докато контролирах страницата на iPad от моя работен плот. Работи добре!
// when in doubt, comment it out! :)
, от звука му, трябва да е подобно на
|_+_|и
|_+_|, нали? Чудили ли сте се защо в документите има коментар, че не трябва да се използва? ИМХО, това беше изненадващо! Очаквам директивата да инициализира модел. Това също прави, но ... то е внедрено по различен начин, тоест не следи стойността на атрибута. Не е нужно да разглеждате изходния код на AngularJS - позволете ми да ви го донеса:
|_+_|
По-малко, отколкото бихте очаквали? Доста четим, освен неудобния синтаксис на директива, нали? Шестият ред е за какво става въпрос.
Сравнете го с ng-show:
|_+_|
Отново шести ред. Има
|_+_|ето, това прави тази директива динамична. В изходния код на AngularJS голяма част от целия код са коментари, които описват код, който е бил предимно четим от самото начало. Вярвам, че това е чудесен начин да научите повече за AngularJS.
Това ръководство, обхващащо най-често срещаните грешки в AngularJS, е почти два пъти по-дълго от останалите ръководства. Така се получи естествено. Търсенето на висококачествени JavaScript инженери отпред е много голямо. AngularJS е толкова горещо в момента и от няколко години заема стабилна позиция сред най-популярните инструменти за разработка. С AngularJS 2.0 на път, той вероятно ще доминира за години напред.
Това, което е чудесно при разработката отпред, е, че е много полезно. Нашата работа се вижда незабавно и хората взаимодействат директно с продуктите, които доставяме. Времето, прекарано в учене JavaScript и вярвам, че трябва да се съсредоточим върху езика JavaScript, е много добра инвестиция. Това е езикът на Интернет. Конкуренцията е супер силна! За нас има един фокус - потребителското изживяване. За да успеем, трябва да покрием всичко.
Изходният код, използван в тези примери, може да бъде изтеглен от GitHub . Можете да го изтеглите и да го направите сами.
Исках да дам кредити на четирима разработчици на издателства, които ме вдъхновиха най-много:
Също така исках да благодаря на всички страхотни хора на FreeNode #angularjs и #javascript канали за много отлични разговори и непрекъсната подкрепа.
И накрая, винаги помнете:
|_+_|
Ако следвате тенденциите в развитието на предния край и сте малко мързеливи - като мен - тогава вероятно се опитвате да не правите всичко на ръка. Проследяване на всички ваши зависимости, обработка на набори от файлове по различни начини, презареждане на браузъра след всяко запазване на файл - има много повече неща за разработване, отколкото просто кодиране.
Така че може да използвате bower и може би npm в зависимост от това как обслужвате приложението си. Има шанс да използвате мрънкане, глътка или закуска. Или баш, което също е готино. Всъщност, може би сте започнали най-новия си проект с някакъв Yeoman генератор!
Това води до въпроса: разбирате ли целия процес на това, което вашата инфраструктура наистина прави? Имате ли нужда от това, което имате, особено ако току-що сте прекарали часове, опитвайки се да поправите функцията за свързване на уеб сървър на живо?
Отделете секунда, за да прецените от какво имате нужда. Всички тези инструменти са само тук, за да ви помогнат, няма друга награда за използването им. По-опитните разработчици, с които говоря, са склонни да опростяват нещата.
Тестовете няма да направят кода ви свободен от съобщения за грешка AngularJS. Това, което те ще направят, е да гарантират, че екипът ви не се сблъсква постоянно с проблеми с регресията.
Тук пиша специално за модулни тестове, не защото смятам, че те са по-важни от e2e тестовете, а защото се изпълняват много по-бързо. Трябва да призная, че процесът, който ще опиша, е много приятен.
Test Driven Development като изпълнение за напр. gulp-karma runner, основно изпълнява всичките ви модулни тестове при всяко записване на файл. Любимият ми начин да пиша тестове е, че първо пиша празни уверения:
angular.reloadWithDebugInfo()
След това пиша или рефакторирам действителния код, след това се връщам към тестовете и попълвам уверенията с действителен тестов код.
Изпълнението на TDD задача в терминал ускорява процеса с около 100%. Единичните тестове се изпълняват за няколко секунди, дори ако имате много от тях. Просто запазете тестовия файл и бегачът ще го вземе, ще оцени вашите тестове и ще предостави незабавна обратна връзка.
При тестовете e2e процесът е много по-бавен. Моят съвет - разделете тестовете e2e на тестови пакети и просто изпълнявайте един по един. Protractor има поддръжка за тях, а по-долу е кодът, който използвам за тестовите си задачи (харесвам глътка).
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 - хромирана хронология
Друг чудесен инструмент, който идва с инструментите за разработчици, е хронологията. Това ще ви позволи да записвате и анализирате ефективността на приложението си на живо, докато го използвате. Резултатът показва, наред с други, използването на паметта, честотата на кадрите и дисекцията на различните процеси, които заемат процесора: зареждане, скриптове, изобразяване и рисуване.
Ако почувствате, че ефективността на приложението ви се влошава, най-вероятно ще можете да намерите причината за това чрез раздела с времевата линия. Просто запишете действията си, които са довели до проблеми с производителността, и вижте какво се случва. Твърде много наблюдатели? Ще видите жълти ленти, които заемат много място. Изтичане на памет? Можете да видите колко памет е изразходвано с течение на времето на графика.
Подробно описание: https://developer.chrome.com/devtools/docs/timeline
C - инспектиране на приложения от разстояние на iOS и Android
Ако разработвате хибридно приложение или адаптивно уеб приложение, можете да осъществите достъп до конзолата на вашето устройство, DOM дървото и всички други инструменти, налични чрез Chrome или Safari dev tools. Това включва WebView и UIWebView.
Първо стартирайте вашия уеб сървър на хост 0.0.0.0, така че да е достъпен от вашата локална мрежа. Активирайте уеб инспектора в настройките. След това свържете устройството си с вашия работен плот и отворете страницата си за локално развитие, като използвате ip на вашето устройство вместо обикновения „localhost“. Това е всичко, вашето устройство вече трябва да е достъпно за вас от браузъра на вашия работен плот.
Тук са подробните инструкции за Android И за iOS, неофициалните ръководства се намират лесно чрез google.
Наскоро имах страхотен опит с browserSync . Той работи по подобен начин за livereload, но всъщност също така синхронизира всички браузъри, които гледат една и съща страница чрез browserSync. Това включва взаимодействие с потребителите, като превъртане, щракване върху бутони и др. Разглеждах изходните данни на приложението за iOS, докато контролирах страницата на iPad от моя работен плот. Работи добре!
// when in doubt, comment it out! :)
, от звука му, трябва да е подобно на
|_+_|и
|_+_|, нали? Чудили ли сте се защо в документите има коментар, че не трябва да се използва? ИМХО, това беше изненадващо! Очаквам директивата да инициализира модел. Това също прави, но ... то е внедрено по различен начин, тоест не следи стойността на атрибута. Не е нужно да разглеждате изходния код на AngularJS - позволете ми да ви го донеса:
|_+_|
По-малко, отколкото бихте очаквали? Доста четим, освен неудобния синтаксис на директива, нали? Шестият ред е за какво става въпрос.
Сравнете го с ng-show:
|_+_|
Отново шести ред. Има
|_+_|ето, това прави тази директива динамична. В изходния код на AngularJS голяма част от целия код са коментари, които описват код, който е бил предимно четим от самото начало. Вярвам, че това е чудесен начин да научите повече за AngularJS.
Това ръководство, обхващащо най-често срещаните грешки в AngularJS, е почти два пъти по-дълго от останалите ръководства. Така се получи естествено. Търсенето на висококачествени JavaScript инженери отпред е много голямо. AngularJS е толкова горещо в момента и от няколко години заема стабилна позиция сред най-популярните инструменти за разработка. С AngularJS 2.0 на път, той вероятно ще доминира за години напред.
Това, което е чудесно при разработката отпред, е, че е много полезно. Нашата работа се вижда незабавно и хората взаимодействат директно с продуктите, които доставяме. Времето, прекарано в учене JavaScript и вярвам, че трябва да се съсредоточим върху езика JavaScript, е много добра инвестиция. Това е езикът на Интернет. Конкуренцията е супер силна! За нас има един фокус - потребителското изживяване. За да успеем, трябва да покрием всичко.
Изходният код, използван в тези примери, може да бъде изтеглен от GitHub . Можете да го изтеглите и да го направите сами.
Исках да дам кредити на четирима разработчици на издателства, които ме вдъхновиха най-много:
Също така исках да благодаря на всички страхотни хора на FreeNode #angularjs и #javascript канали за много отлични разговори и непрекъсната подкрепа.
И накрая, винаги помнете:
|_+_|