AngularJS Geliştiricilerinin Yaptığı En Yaygın 18 Hata

Yayınlanan: 2022-03-11

Tek sayfa uygulamaları, ön uç geliştiricilerin daha iyi yazılım mühendisleri olmasını talep eder. CSS ve HTML artık en büyük endişe değil, aslında artık tek bir endişe yok. Ön uç geliştiricinin XHR'leri, uygulama mantığını (modeller, görünümler, denetleyiciler), performansı, animasyonları, stilleri, yapıyı, SEO'yu ve harici hizmetlerle entegrasyonu ele alması gerekir. Tüm bunların birleşiminden ortaya çıkan sonuç, her zaman öncelik verilmesi gereken Kullanıcı Deneyimi'dir (UX).

AngularJS çok güçlü bir çerçevedir. GitHub'da en çok yıldızlı üçüncü depodur. Kullanmaya başlamak zor değil, ancak gerçekleştirmesi amaçlanan amaçların anlaşılmasını gerektirir. Artık AngularJS geliştiricileri bellek tüketimini göz ardı edemez, çünkü artık navigasyonda sıfırlanmayacaktır. Bu, web geliştirmenin öncüsüdür. Hadi kucaklayalım!

Yaygın AngularJS hataları

Yaygın Hata 1: DOM Üzerinden Kapsama Erişme

Üretim için önerilen birkaç optimizasyon ayarı vardır. Bunlardan biri hata ayıklama bilgilerini devre dışı bırakmaktır.

DebugInfoEnabled , varsayılan olarak true olan ve DOM düğümleri aracılığıyla kapsam erişimine izin veren bir ayardır. Bunu JavaScript konsolu aracılığıyla denemek istiyorsanız, bir DOM öğesi seçin ve kapsamına şu şekilde erişin:

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

CSS ile jQuery kullanılmadığında bile faydalı olabilir, ancak konsol dışında kullanılmamalıdır. Bunun nedeni, $compileProvider.debugInfoEnabled öğesi false olarak ayarlandığında, bir DOM düğümünde .scope() çağrılması undefined döndürecektir.

Bu, üretim için önerilen birkaç seçenekten biridir.

Lütfen, üretimdeyken bile, kapsama konsoldan erişmeye devam edebileceğinizi unutmayın. Konsoldan angular.reloadWithDebugInfo() çağırın ve uygulama tam da bunu yapacaktır.

Yaygın Hata #2: Orada Nokta Olmaması

Muhtemelen ng-modelinizde nokta yoksa yanlış yaptığınızı okumuşsunuzdur. Miras söz konusu olduğunda, bu ifade genellikle doğrudur. Kapsamların, JavaScript'e özgü prototip bir kalıtım modeli vardır ve iç içe kapsamlar AngularJS'de ortaktır. Birçok yönerge, ngRepeat , ngIf ve ngController gibi alt kapsamlar oluşturur. Bir modeli çözümlerken, arama geçerli kapsamda başlar ve $rootScope kadar her ana kapsamdan geçer.

Ancak, yeni bir değer ayarlarken, ne tür bir model (değişken) değiştirmek istediğimize bağlıdır. Model bir ilkel ise, alt kapsam sadece yeni bir model oluşturacaktır. Ancak değişiklik bir model nesnesinin bir özelliğindeyse, üst kapsamlardaki arama, başvurulan nesneyi bulur ve gerçek özelliğini değiştirir. Geçerli kapsamda yeni bir model ayarlanmaz, bu nedenle maskeleme olmaz:

 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>

"İlkel ayarla" etiketli düğmeye tıklamak, iç kapsamda foo'yu 2'ye ayarlayacaktır, ancak dış kapsamda foo'yu değiştirmeyecektir.

“Nesneyi değiştir” etiketli düğmeye tıklamak, bar özelliğini üst kapsamdan değiştirecektir. İç kapsamda değişken olmadığı için gölgelenme olmayacak ve her iki kapsamda bar için görünür değer 3 olacaktır.

Bunu yapmanın başka bir yolu, her kapsamdan ana kapsamlara ve kök Kapsama başvurulması gerçeğinden yararlanmaktır. $parent ve $root nesneleri, sırasıyla doğrudan görünümden üst kapsam ve $rootScope erişmek için kullanılabilir. Güçlü bir yol olabilir, ancak akışta belirli bir kapsamı hedefleme sorunu nedeniyle hayranı değilim. Bir kapsama özgü özellikleri ayarlamanın ve bunlara erişmenin başka bir yolu daha vardır - controllerAs sözdizimini kullanarak.

Yaygın Hata #3: controllerAs Sözdizimini Kullanmamak

Modelleri enjekte edilen $ kapsamı yerine bir denetleyici nesnesi kullanacak şekilde atamanın alternatif ve en verimli yolu. Kapsam enjekte etmek yerine şu şekilde modeller tanımlayabiliriz:

 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>

Bu çok daha az kafa karıştırıcı. Özellikle, iç içe geçmiş durumlarda olduğu gibi, birçok iç içe kapsam olduğunda.

controllerAs sözdiziminde daha fazlası var.

Yaygın Hata #4: controllerAs Sözdizimini Tam Olarak Kullanmamak

Denetleyici nesnesinin nasıl açığa çıktığıyla ilgili birkaç uyarı vardır. Temelde, tıpkı normal bir model gibi, denetleyicinin kapsamında ayarlanan bir nesnedir.

Denetleyici nesnesinin bir özelliğini izlemeniz gerekiyorsa, bir işlevi izleyebilirsiniz ancak buna gerek yoktur. İşte bir örnek:

 function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }

Sadece yapmak daha kolaydır:

 function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }

Bu, kapsam zincirinin aşağısında da, bir alt denetleyiciden MC'ye erişebileceğiniz anlamına gelir:

 function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }

Ancak bunu yapabilmek için controllerAs için kullandığınız kısaltma ile tutarlı olmanız gerekir. Bunu ayarlamanın en az üç yolu vardır. İlkini zaten gördünüz:

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

Ancak, ui-router kullanıyorsanız, bu şekilde bir denetleyici belirtmek hataya açıktır. Durumlar için, kontrolörler durum konfigürasyonunda belirtilmelidir:

 angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });

Açıklama eklemenin başka bir yolu var:

 (…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })

Aynısını direktiflerde de yapabilirsiniz:

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

Açıklama eklemenin diğer yolu da daha az özlü olmasına rağmen geçerlidir:

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

Yaygın Hata #5: Güç İçin UI-ROUTER ile Adlandırılmış Görünümleri Kullanmamak”

AngularJS için fiili yönlendirme çözümü şimdiye kadar ui-router olmuştur. Bir süre önce çekirdekten kaldırılan ngRoute modülü, daha karmaşık yönlendirme için fazla basitti.

Yeni bir NgRouter yolda, ancak yazarlar hala üretim için çok erken olduğunu düşünüyor. Bunu yazarken, kararlı Açısal 1.3.15 ve ui-router kayaları.

Ana nedenler:

  • harika durum yuvalama
  • rota soyutlama
  • isteğe bağlı ve gerekli parametreler

Burada AngularJS hatalarından kaçınmak için durum yuvalamayı ele alacağım.

Bunu karmaşık ancak standart bir kullanım durumu olarak düşünün. Ana sayfa görünümü ve ürün görünümü olan bir uygulama var. Ürün görünümünün üç ayrı bölümü vardır: giriş, pencere öğesi ve içerik. Durum arasında geçiş yaparken widget'ın kalıcı olmasını ve yeniden yüklenmemesini istiyoruz. Ancak içerik yeniden yüklenmeli.

Aşağıdaki HTML ürün dizini sayfa yapısını göz önünde bulundurun:

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

Bu, HTML kodlayıcıdan alabileceğimiz bir şey ve şimdi onu dosyalara ve durumlara ayırmamız gerekiyor. Genelde, gerekirse küresel verileri tutan soyut bir MAIN durumu olduğu kuralına uyuyorum. $rootScope yerine bunu kullanın. Ana durum, her sayfada gerekli olan statik HTML'yi de tutacaktır. index.html'i temiz tutuyorum.

 <!— 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>

Ardından ürün dizin sayfasını görelim:

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

Gördüğünüz gibi, ürün dizini sayfasının adlandırılmış üç görünümü vardır. Biri giriş için, biri widget için ve biri de ürün için. Teknik özellikleri karşılıyoruz! Şimdi yönlendirmeyi ayarlayalım:

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

İlk yaklaşım bu olurdu. Şimdi, main.product.index ve main.product.details arasında geçiş yapıldığında ne olur? İçerik ve pencere öğesi yeniden yüklenir, ancak yalnızca içeriği yeniden yüklemek istiyoruz. Bu sorunluydu ve geliştiriciler aslında tam da bu işlevi destekleyecek yönlendiriciler oluşturdular. Bunun isimlerinden biri yapışkan görüşlerdi . Neyse ki, ui-router bunu kutudan çıktığı haliyle mutlak adlandırılmış görünüm hedefleme ile desteklemektedir.

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

Durum tanımını yine soyut olan ebeveyn görünümüne taşıyarak, normalde o çocuğun kardeşlerini etkileyen URL'leri değiştirirken alt görünümün yeniden yüklenmesini önleyebiliriz. Elbette, widget basit bir yönerge olabilir. Ama mesele şu ki, başka bir karmaşık iç içe durum da olabilir.

Bunu $urlRouterProvider.deferIntercept() kullanarak yapmanın başka bir yolu daha var, ancak durum yapılandırmasını kullanmanın aslında daha iyi olduğunu düşünüyorum. Yolları kesmekle ilgileniyorsanız, StackOverflow hakkında küçük bir eğitim yazdım.

Yaygın Hata #6: Açısal Dünyada Her Şeyi Anonim İşlevler Kullanarak Bildirmek

Bu hata daha hafif düzeydedir ve AngularJS hata mesajlarından kaçınmaktan çok bir tarz sorunudur. Angular internal'ın bildirimlerine nadiren anonim işlevler ilettiğimi daha önce fark etmiş olabilirsiniz. Genelde önce bir işlevi tanımlarım ve sonra onu iletirim.

Bu, işlevlerden daha fazlasını ifade eder. Bu yaklaşımı, özellikle Airbnb'nin ve Todd Motto'nun stil kılavuzlarını okumaktan aldım. Birkaç avantajı olduğuna ve neredeyse hiç dezavantajı olmadığına inanıyorum.

Her şeyden önce, bir değişkene atanmışlarsa, işlevlerinizi ve nesnelerinizi çok daha kolay manipüle edebilir ve değiştirebilirsiniz. İkincisi, kod daha temizdir ve kolayca dosyalara bölünebilir. Bu, sürdürülebilirlik anlamına gelir. Genel ad alanını kirletmek istemiyorsanız, her dosyayı IIFE'lere sarın. Üçüncü neden test edilebilirliktir. Bu örneği düşünün:

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

Şimdi publicMethod1 ile dalga geçebiliriz, ancak açığa çıktığına göre bunu neden yapmalıyız? Mevcut yöntemi gözetlemek daha kolay olmaz mıydı? Ancak, yöntem aslında başka bir işlevdir - ince bir sarıcı. Bu yaklaşıma bir göz atın:

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

Bu sadece stille ilgili değildir, çünkü aslında kod daha yeniden kullanılabilir ve deyimseldir. Geliştirici daha fazla ifade gücü elde eder. Tüm kodu bağımsız bloklara bölmek bunu kolaylaştırır.

Yaygın Hata #7: İşçileri Kullanarak Açısal AKA'da Ağır İşlemler Yapmak

Bazı senaryolarda, çok sayıda karmaşık nesneyi bir dizi filtreden, dekoratörden ve son olarak bir sıralama algoritmasından geçirerek işlemek gerekebilir. Bir kullanım durumu, uygulamanın çevrimdışı çalışması gerektiği veya veri görüntüleme performansının önemli olduğu durumlardır. JavaScript tek iş parçacıklı olduğundan, tarayıcıyı dondurmak nispeten kolaydır.

Web çalışanları ile bundan kaçınmak da kolaydır. Bunu özellikle AngularJS için işleyen popüler kütüphaneler yok gibi görünüyor. Uygulaması kolay olduğu için en iyisi olabilir.

İlk olarak, servisi ayarlayalım:

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

Şimdi, işçi:

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

Şimdi, hizmeti her zamanki gibi enjekte edin ve scoringService.scoreItems() , söz veren herhangi bir hizmet yönteminde yaptığınız gibi ele alın. Ağır işlem ayrı bir iş parçacığı üzerinde gerçekleştirilecek ve UX'e herhangi bir zarar verilmeyecektir.

Nelere dikkat edilmelidir:

  • Kaç işçinin ortaya çıkacağına dair genel bir kural yok gibi görünüyor. Bazı geliştiriciler 8'in iyi bir sayı olduğunu iddia ediyor, ancak çevrimiçi bir hesap makinesi kullanın ve kendinize uygun
  • eski tarayıcılarla uyumluluğu kontrol edin
  • 0 sayısını hizmetten çalışana geçirirken bir sorunla karşılaşıyorum. Geçirilen özelliğe .toString() uyguladım ve düzgün çalıştı.

Yaygın Hata #8: Aşırı Kullanım ve Yanlış Anlama Çözümleri

Çözümler, görünümün yüklenmesine fazladan zaman ekler. Ön uç uygulamasının yüksek performansının birincil hedefimiz olduğuna inanıyorum. Uygulama, API'den gelen verileri beklerken görünümün bazı bölümlerini oluşturmak sorun olmamalıdır.

Bu kurulumu düşünün:

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

Konsol çıktısı şöyle olacaktır:

 Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executed

Hangi temelde şu anlama gelir:

  • Çözümler eşzamansız olarak yürütülür
  • Bir yürütme sırasına güvenemeyiz (veya en azından biraz esnetmemiz gerekir)
  • Tüm kararlar, soyut olmasalar bile, işlerini yapana kadar tüm durumlar bloke edilir.

Bu, kullanıcının herhangi bir çıktı görmeden önce tüm bağımlılıkları beklemesi gerektiği anlamına gelir. Bu verilere sahip olmamız gerekiyor, elbette, tamam. Görünümden önce kesinlikle gerekliyse, onu bir .run() bloğuna koyun. Aksi takdirde, kontrol cihazından servisi aramanız ve yarı yüklü durumu zarif bir şekilde ele almanız yeterlidir. Devam eden işi görmek - ve denetleyici zaten yürütüldü, yani aslında ilerleme - uygulamanın durmasından daha iyidir.

Yaygın Hata #9: Uygulamayı Optimize Etmemek - Üç Örnek

a) Modellere kaydırıcı eklemek gibi çok fazla özet döngüsüne neden olmak

Bu, AngularJS hatalarına neden olabilecek genel bir sorundur, ancak bunu kaydırıcı örneğinde tartışacağım. Bu kaydırıcı kitaplığını, açısal aralık kaydırıcısını kullanıyordum çünkü genişletilmiş işlevselliğe ihtiyacım vardı. Bu yönergenin minimal versiyonunda şu sözdizimi vardır:

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

Denetleyicide aşağıdaki kodu göz önünde bulundurun:

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

Yani yavaş çalışıyor. Gündelik çözüm, girişte bir zaman aşımı ayarlamak olacaktır. Ancak bu her zaman kullanışlı değildir ve bazen her durumda gerçek model değişikliğini geciktirmek istemeyiz.

Bu nedenle, çalışma modelini zaman aşımına uğradığında değiştirmek için geçici bir model ekleyeceğiz:

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

ve denetleyicide:

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

b) $applyAsync kullanmamak

AngularJS, $digest() öğesini çağırmak için bir yoklama mekanizmasına sahip değildir. Yalnızca, kodumuzu değerlendiren ve daha sonra bir özet çağıran yönergeleri (örneğin ng-click , input ), hizmetleri ( $timeout , $http ) ve yöntemleri ( $watch ) kullandığımız için yürütülür.

.$applyAsync() in yaptığı şey, ifadelerin çözümlenmesini bir sonraki $digest() döngüsüne kadar geciktirmesidir; bu, gerçekte ~10ms olan bir 0 zaman aşımından sonra tetiklenir.

Şimdi applyAsync kullanmanın iki yolu vardır. $http istekleri için otomatik bir yol ve geri kalanı için manuel bir yol.

Aynı zamanda dönen tüm http isteklerinin tek bir özette çözülmesini sağlamak için şunları yapın:

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

Manuel yol, gerçekte nasıl çalıştığını gösterir. Bir Vanilla JS olay dinleyicisine veya bir jQuery .click() veya başka bir harici kitaplığa yapılan geri çağrıda çalışan bir işlevi düşünün. Modelleri çalıştırdıktan ve değiştirdikten sonra, henüz bir $apply() içine sarmadıysanız, $scope.$root.$digest() ( $rootScope.$digest() ) veya en azından $scope.$digest() çağırmanız gerekir. $scope.$digest() . Aksi takdirde hiçbir değişiklik göremezsiniz.

Bunu bir akışta birden çok kez yaparsanız, yavaş çalışmaya başlayabilir. Bunun yerine ifadelerde $scope.$applyAsync() çağırmayı düşünün. Hepsi için yalnızca bir özet döngüsü ayarlayacaktır.

c) Görüntülerin ağır işlenmesi

Kötü performansla karşılaşırsanız, Chrome Geliştirici Araçları'ndan Zaman Çizelgesi'ni kullanarak nedenini araştırabilirsiniz. Hata #17'de bu araç hakkında daha fazla yazacağım. Kayıttan sonra zaman çizelgesi grafiğinize yeşil renk hakimse, performans sorunlarınız görüntülerin işlenmesiyle ilgili olabilir. Bu, kesinlikle AngularJS ile ilgili değildir, ancak AngularJS performans sorunlarının (grafikte çoğunlukla sarı renkte olacaktır) üzerine olabilir. Ön uç mühendisleri olarak, nihai projenin tamamını düşünmemiz gerekiyor.

Değerlendirmek için bir dakikanızı ayırın:

  • paralaks kullanıyor musunuz?
  • Birbiriyle örtüşen birkaç içerik katmanınız var mı?
  • Resimlerinizi hareket ettiriyor musunuz?
  • Görüntüleri ölçekliyor musunuz (örn. arka plan boyutunda)?
  • Görüntüleri döngülerde yeniden boyutlandırıyor musunuz ve belki de yeniden boyutlandırmada özet döngülerine neden oluyor musunuz?

Yukarıdakilerden en az üçüne “evet” yanıtı verdiyseniz, kolaylaştırmayı düşünün. Belki çeşitli görüntü boyutları sunabilir ve hiç yeniden boyutlandırmayabilirsiniz. Belki “transform: translateZ(0)” zorlamalı GPU işleme hack'ini ekleyebilirsiniz. Veya işleyiciler için requestAnimationFrame kullanın.

Yaygın Hata #10: jQuerying - Ayrılmış DOM Ağacı

Çoğu zaman, jQuery'nin AngularJS ile kullanılmasının tavsiye edilmediğini ve bundan kaçınılması gerektiğini duymuşsunuzdur. Bu açıklamaların arkasındaki nedeni anlamak zorunludur. Görebildiğim kadarıyla en az üç sebep var, ancak hiçbiri gerçek engelleyici değil.

Sebep 1: jQuery kodunu çalıştırdığınızda, $digest() kendiniz çağırmanız gerekir. Çoğu durumda, AngularJS için özel olarak hazırlanmış ve Angular içinde jQuery'den daha iyi kullanılabilen bir AngularJS çözümü vardır (örn. ng-click veya olay sistemi).

Sebep 2: Uygulamayı oluşturma hakkındaki düşünce yöntemi. Gezinirken yeniden yüklenen web sitelerine JavaScript ekliyorsanız, bellek tüketimi konusunda çok fazla endişelenmenize gerek yoktu. Tek sayfalık uygulamalarla endişelenmenize gerek yok. Temizlemezseniz, uygulamanızda birkaç dakikadan fazla zaman harcayan kullanıcılar artan performans sorunları yaşayabilir.

Sebep 3: Temizlemek aslında yapılması ve analiz edilmesi en kolay şey değildir. Komut dosyasından (tarayıcıda) bir çöp toplayıcı çağırmanın bir yolu yoktur. Müstakil DOM ağaçları ile sonuçlanabilir. Bir örnek oluşturdum (jQuery, index.html'ye yüklendi):

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

Bu, bazı metinleri çıkaran basit bir yönergedir. Altında, direktifi manuel olarak yok edecek bir düğme var.

Bu nedenle, yönerge kaldırıldığında, DOM ağacına kapsam.toBeDetached içinde bir referans kalır. Chrome geliştirme araçlarında, "profiller" sekmesine erişir ve ardından "yığın anlık görüntüsünü alır", çıktıda şunu görürsünüz:

Birkaçıyla yaşayabilirsin, ama bir ton varsa kötü. Özellikle herhangi bir nedenle, örnekte olduğu gibi, onu kapsamda saklarsanız. Tüm DOM, her özette değerlendirilecektir. Sorunlu ayrılmış DOM ağacı, 4 düğümlü ağaçtır. Peki bu nasıl çözülebilir?

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

4 girişli müstakil DOM ağacı kaldırıldı!

Bu örnekte, yönerge aynı kapsamı kullanır ve DOM öğesini kapsamda depolar. Bunu böyle göstermek benim için daha kolaydı. Bir değişkende saklayabileceğiniz için her zaman o kadar kötü olmaz. Bununla birlikte, bu değişkene veya aynı işlev kapsamından başka herhangi bir değişkene başvuran herhangi bir kapatma devam ederse, yine de bellekte yer kaplar.

Yaygın Hata #11: Yalıtılmış Kapsamın Aşırı Kullanımı

Tek bir yerde kullanılacağını bildiğiniz veya hangi ortamda kullanılırsa kullanılsın çakışmasını beklemediğiniz bir yönergeye ihtiyaç duyduğunuzda, yalıtılmış kapsam kullanmanıza gerek yoktur. Son zamanlarda yeniden kullanılabilir bileşenler oluşturma eğilimi var, ancak çekirdek açısal yönergelerin hiç yalıtılmış kapsam kullanmadığını biliyor muydunuz?

Bunun iki ana nedeni vardır: bir öğeye iki yalıtılmış kapsam yönergesi uygulayamazsınız ve yuvalama/miras/olay işleme ile ilgili sorunlarla karşılaşabilirsiniz. Özellikle transklüzyonla ilgili olarak - etkiler beklediğiniz gibi olmayabilir.

Yani bu başarısız olur:

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

Ve sadece bir yönerge kullansanız bile, ne yalıtılmış kapsam modellerinin ne de isolatedScopeDirective içinde yayınlanan olayların AnotherController tarafından kullanılamayacağını fark edeceksiniz. Bu üzücü olmakla birlikte, çalışmasını sağlamak için dönüştürme büyüsünü esnetebilir ve kullanabilirsiniz - ancak çoğu kullanım durumunda, tecrit etmeye gerek yoktur.

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

Peki şimdi iki soru:

  1. Aynı kapsam yönergesinde ana kapsam modellerini nasıl işleyebilirsiniz?
  2. Yeni model değerlerini nasıl somutlaştırabilirsiniz?

İki yol vardır, her ikisinde de değerleri niteliklere iletirsiniz. Bu MainController'ı düşünün:

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

Bu görünümü kontrol eden:

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

"İzle-özniteliği"nin enterpolasyonlu olmadığına dikkat edin. JS büyüsü sayesinde hepsi işe yarıyor. İşte direktif tanımı:

 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 , tırnak işaretleri olmadan scope.$watch() öğesine iletildiğine dikkat edin! Bu, aslında $ MC.foo dizesi olduğu anlamına gelir! Ancak çalışır, çünkü $watch() öğesine iletilen herhangi bir dize kapsam açısından değerlendirilir ve kapsamda MC.foo kullanılabilir. Bu aynı zamanda AngularJS çekirdek yönergelerinde özniteliklerin izlenmesinin en yaygın yoludur.

Şablon için github'daki koda bakın ve daha da fazla harikalık için $parse ve $eval bakın.

Yaygın Hata #12: Kendinizden Sonra Temizlik Yapmamak - İzleyiciler, Aralıklar, Zaman Aşımları ve Değişkenler

AngularJS sizin adınıza bazı işler yapar, ancak hepsini değil. Aşağıdakilerin manuel olarak temizlenmesi gerekir:

  • Geçerli kapsama bağlı olmayan tüm izleyiciler (örneğin, $rootScope'a bağlı)
  • Aralıklar
  • zaman aşımları
  • Direktiflerde DOM'a başvuran değişkenler
  • Tehlikeli jQuery eklentileri, örneğin JavaScript $destroy olayına tepki veren işleyicileri olmayanlar

Bunu manuel olarak yapmazsanız, beklenmeyen davranışlarla ve bellek sızıntılarıyla karşılaşırsınız. Daha da kötüsü - bunlar anında görünmeyecek, ancak sonunda ortaya çıkacaklar. Murphy kanunu.

Şaşırtıcı bir şekilde, AngularJS tüm bunlarla başa çıkmak için kullanışlı yollar sunar:

 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 olayına dikkat edin. AngularJS gibi denir, ancak ayrı olarak ele alınır. Kapsam $watchers, jQuery olayına tepki vermeyecektir.

Yaygın Hata #13: Çok Fazla Gözcü Tutmak

Bu şimdi oldukça basit olmalı. Burada anlaşılması gereken bir şey var: $digest() . Her {{ model }} bağlaması için AngularJS bir izleyici oluşturur. Her özet fazında, bu tür her bağlama değerlendirilir ve önceki değerle karşılaştırılır. Buna kirli kontrol denir ve $digest'in yaptığı da budur. Değer son kontrolden sonra değiştiyse, izleyici geri araması başlatılır. 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.

İlk olarak, yerel ağınızdan erişilebilir olması için web sunucunuzu ana bilgisayar 0.0.0.0 üzerinde başlatın. Ayarlarda web denetçisini etkinleştirin. Ardından cihazınızı masaüstünüze bağlayın ve normal “localhost” yerine makinenizin ipini kullanarak yerel geliştirme sayfanıza erişin. Tek yapmanız gereken bu, cihazınız artık masaüstünüzün tarayıcısından sizin için erişilebilir olmalıdır.

İşte Android için ayrıntılı talimatlar ve iOS için resmi olmayan kılavuzlar google aracılığıyla kolayca bulunabilir.

Geçenlerde browserSync ile harika bir deneyim yaşadım. Canlı yeniden yüklemeye benzer şekilde çalışır, ancak aynı sayfayı görüntüleyen tüm tarayıcıları browserSync aracılığıyla gerçekten senkronize eder. Bu, kaydırma, düğmelere tıklama vb. gibi kullanıcı etkileşimlerini içerir. iPad'deki sayfayı masaüstümden kontrol ederken iOS uygulamasının günlük çıktısına bakıyordum. Güzel çalıştı!

Yaygın Hata #18: NG-INIT Örneğindeki Kaynak Kodu Okumamak

Ng-init , sesinden, ng-if ve ng-repeat benzer olmalıdır, değil mi? Dokümanlarda neden kullanılmaması gerektiğine dair bir yorum olduğunu hiç merak ettiniz mi? IMHO bu şaşırtıcıydı! Yönergenin bir modeli başlatmasını beklerdim. Bunu da yapar ama… farklı bir şekilde uygulanır, yani öznitelik değerini izlemez. AngularJS kaynak koduna göz atmanıza gerek yok - size getirmeme izin verin:

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

Beklediğinizden daha az mı? Garip yönerge sözdiziminin yanı sıra oldukça okunabilir, değil mi? Altıncı satır her şeyle ilgili.

Bunu ng-show ile karşılaştırın:

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

Yine altıncı satır. Orada bir $watch var, bu yönergeyi dinamik yapan da bu. AngularJS kaynak kodunda, tüm kodun büyük bir kısmı, çoğunlukla en baştan okunabilen kodu açıklayan yorumlardır. AngularJS hakkında bilgi edinmenin harika bir yolu olduğuna inanıyorum.

Çözüm

En yaygın AngularJS hatalarını kapsayan bu kılavuz, diğer kılavuzlardan neredeyse iki kat daha uzundur. Doğal olarak böyle çıktı. Yüksek kaliteli JavaScript ön uç mühendislerine olan talep çok yüksektir. AngularJS şu anda çok popüler ve birkaç yıldır en popüler geliştirme araçları arasında istikrarlı bir konuma sahip. AngularJS 2.0 yolda, muhtemelen önümüzdeki yıllarda hakim olacak.

Ön uç geliştirme ile ilgili harika olan şey, çok ödüllendirici olmasıdır. Çalışmalarımız anında görülebilir ve insanlar sunduğumuz ürünlerle doğrudan etkileşime girer. JavaScript öğrenmek için harcanan zaman ve JavaScript diline odaklanmamız gerektiğine inanıyorum, çok iyi bir yatırım. İnternetin dilidir. Rekabet süper güçlü! Bizim için bir odak noktası var - kullanıcı deneyimi. Başarılı olmak için her şeyi kapsamalıyız.

Bu örneklerde kullanılan kaynak kodu GitHub'dan indirilebilir. İndirmekten çekinmeyin ve kendiniz yapın.

Bana en çok ilham veren dört yayın geliştiricisine kredi vermek istedim:

  • Ben Nadel
  • Todd Sloganı
  • Pascal Precht
  • derin panda

Ayrıca FreeNode #angularjs ve #javascript kanallarındaki harika insanlara birçok mükemmel sohbet ve sürekli destek için teşekkür etmek istedim.

Ve son olarak, her zaman hatırla:

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