Die 18 häufigsten Fehler, die AngularJS-Entwickler machen
Veröffentlicht: 2022-03-11Single-Page-Apps fordern von den Front-End-Entwicklern, bessere Software-Ingenieure zu werden. CSS und HTML sind nicht mehr die größte Sorge, tatsächlich gibt es nicht mehr nur eine einzige Sorge. Der Front-End-Entwickler muss sich um XHRs, Anwendungslogik (Modelle, Ansichten, Controller), Leistung, Animationen, Stile, Struktur, SEO und die Integration mit externen Diensten kümmern. Das Ergebnis, das aus all dem zusammenkommt, ist die User Experience (UX), die immer priorisiert werden sollte.
AngularJS ist ein sehr mächtiges Framework. Es ist das dritthäufigste Repository auf GitHub. Der Einstieg ist nicht schwierig, aber die Ziele, die damit erreicht werden sollen, erfordern Verständnis. AngularJS-Entwickler können den Speicherverbrauch nicht länger ignorieren, da er bei der Navigation nicht mehr zurückgesetzt wird. Dies ist die Avantgarde der Webentwicklung. Umarmen wir es!
Häufiger Fehler Nr. 1: Zugriff auf den Bereich über das DOM
Es gibt ein paar Optimierungen, die für die Produktion empfohlen werden. Eine davon ist das Deaktivieren von Debug-Informationen.
DebugInfoEnabled ist eine Einstellung, die standardmäßig auf „true“ gesetzt ist und Bereichszugriff über DOM-Knoten ermöglicht. Wenn Sie dies über die JavaScript-Konsole versuchen möchten, wählen Sie ein DOM-Element aus und greifen Sie auf seinen Bereich zu mit:
angular.element(document.body).scope() Es kann nützlich sein, auch wenn jQuery nicht mit seinem CSS verwendet wird, sollte aber nicht außerhalb der Konsole verwendet werden. Der Grund dafür ist, dass, wenn $compileProvider.debugInfoEnabled auf false gesetzt ist, der Aufruf .scope() auf einem DOM-Knoten undefined zurückgibt.
Das ist eine der wenigen empfohlenen Optionen für die Produktion.
Bitte beachten Sie, dass Sie auch während der Produktion weiterhin über die Konsole auf den Bereich zugreifen können. Rufen Sie angular.reloadWithDebugInfo() von der Konsole aus auf und die App wird genau das tun.
Häufiger Fehler Nr. 2: Keinen Punkt drin haben
Sie haben wahrscheinlich gelesen, dass Sie es falsch gemacht haben, wenn Sie keinen Punkt in Ihrem ng-Modell hatten. Wenn es um das Erbe geht, trifft diese Aussage oft zu. Bereiche haben ein prototypisches Vererbungsmodell, das typisch für JavaScript ist, und verschachtelte Bereiche sind bei AngularJS üblich. Viele Direktiven erstellen untergeordnete Bereiche wie ngRepeat , ngIf und ngController . Beim Auflösen eines Modells beginnt die Suche im aktuellen Bereich und durchläuft jeden übergeordneten Bereich bis hin zu $rootScope .
Was jedoch beim Festlegen eines neuen Werts passiert, hängt davon ab, welche Art von Modell (Variable) wir ändern möchten. Wenn das Modell ein Primitiv ist, erstellt der untergeordnete Bereich einfach ein neues Modell. Wenn sich die Änderung jedoch auf eine Eigenschaft eines Modellobjekts bezieht, findet die Suche in übergeordneten Bereichen das Objekt, auf das verwiesen wird, und ändert seine tatsächliche Eigenschaft. Für den aktuellen Bereich würde kein neues Modell festgelegt, sodass keine Maskierung erfolgen würde:
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>Durch Klicken auf die Schaltfläche „Primitiv festlegen“ wird foo im inneren Bereich auf 2 gesetzt, aber foo im äußeren Bereich nicht geändert.
Durch Klicken auf die Schaltfläche „Objekt ändern“ wird die Balkeneigenschaft des übergeordneten Bereichs geändert. Da sich im inneren Bereich keine Variable befindet, tritt keine Schattenbildung auf, und der sichtbare Wert für bar ist in beiden Bereichen 3.
Eine andere Möglichkeit, dies zu tun, besteht darin, die Tatsache zu nutzen, dass die übergeordneten Bereiche und der Stammbereich von jedem Bereich referenziert werden. Die Objekte $parent und $root können verwendet werden, um direkt aus der Ansicht auf den übergeordneten Geltungsbereich bzw. $rootScope . Es mag ein mächtiger Weg sein, aber ich bin kein Fan davon, da es ein Problem mit der Ausrichtung auf einen bestimmten Bereich im Stream gibt. Es gibt eine andere Möglichkeit, spezifische Eigenschaften eines Bereichs festzulegen und darauf zuzugreifen – mithilfe der controllerAs -Syntax.
Häufiger Fehler Nr. 3: ControllerAs-Syntax nicht verwenden
Die alternative und effizienteste Methode zum Zuweisen von Modellen zur Verwendung eines Controller-Objekts anstelle des injizierten $scope. Anstatt Geltungsbereich einzufügen, können wir Modelle wie folgt definieren:
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>Das ist viel weniger verwirrend. Besonders wenn es viele verschachtelte Bereiche gibt, wie es bei verschachtelten Zuständen der Fall sein kann.
Die ControllerAs-Syntax hat noch mehr zu bieten.
Häufiger Fehler Nr. 4: Nicht vollständige Nutzung der ControllerAs-Syntax
Es gibt ein paar Vorbehalte bei der Darstellung des Controller-Objekts. Es ist im Grunde ein Objekt, das auf den Bereich des Controllers gesetzt wird, genau wie ein normales Modell.
Wenn Sie eine Eigenschaft des Controller-Objekts überwachen müssen, können Sie eine Funktion überwachen, müssen dies aber nicht. Hier ist ein Beispiel:
function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }Es ist einfacher, einfach zu tun:
function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }Das bedeutet, dass Sie auch in der Bereichskette von einem untergeordneten Controller aus auf MC zugreifen können:
function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }Um dies tun zu können, müssen Sie jedoch mit dem Akronym übereinstimmen, das Sie für ControllerAs verwenden. Es gibt mindestens drei Möglichkeiten, es einzustellen. Den ersten hast du schon gesehen:
<div ng-controller="MainController as MC"> … </div> Wenn Sie jedoch ui-router verwenden, ist die Angabe eines Controllers auf diese Weise fehleranfällig. Für Zustände sollten Controller in der Zustandskonfiguration angegeben werden:
angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });Es gibt eine andere Möglichkeit, Anmerkungen zu machen:
(…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })Sie können dasselbe in Direktiven tun:
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);Die andere Art zu kommentieren ist auch gültig, wenn auch weniger prägnant:
function testForToptal() { return { controller: 'AnotherController', controllerAs: 'AC', template: '<p>{{ AC.text }}</p>' }; }Häufiger Fehler Nr. 5: Benannte Ansichten nicht mit UI-ROUTER für Power verwenden“
Die De-facto-Routing-Lösung für AngularJS war bisher der ui-router . Das ngRoute-Modul, das vor einiger Zeit aus dem Kern entfernt wurde, war zu einfach für ein anspruchsvolleres Routing.
Es ist ein neuer NgRouter auf dem Weg, aber die Autoren halten es noch für zu früh für die Produktion. Während ich dies schreibe, ist das stabile Angular 1.3.15 und ui-router rockt.
Die Hauptgründe:
- tolle Zustandsverschachtelung
- Routenabstraktion
- optionale und erforderliche Parameter
Hier werde ich die Verschachtelung von Zuständen behandeln, um AngularJS-Fehler zu vermeiden.
Betrachten Sie dies als einen komplexen, aber standardmäßigen Anwendungsfall. Es gibt eine App, die eine Startseitenansicht und eine Produktansicht hat. Die Produktansicht besteht aus drei separaten Abschnitten: dem Intro, dem Widget und dem Inhalt. Wir möchten, dass das Widget bestehen bleibt und nicht neu geladen wird, wenn zwischen den Zuständen gewechselt wird. Aber der Inhalt sollte neu geladen werden.
Betrachten Sie die folgende Struktur der HTML-Produktindexseite:
<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>Das könnten wir vom HTML-Coder bekommen und müssen es jetzt in Dateien und Zustände aufteilen. Ich gehe im Allgemeinen von der Konvention aus, dass es einen abstrakten MAIN-Zustand gibt, der die globalen Daten bei Bedarf behält. Verwenden Sie das anstelle von $rootScope. Der Hauptstatus behält auch statisches HTML bei, das auf jeder Seite erforderlich ist. Ich halte index.html sauber.
<!— 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>Sehen wir uns dann die Produktindexseite an:
<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>Wie Sie sehen können, hat die Produktindexseite drei benannte Ansichten. Eine für das Intro, eine für das Widget und eine für das Produkt. Wir erfüllen die Spezifikationen! Also richten wir jetzt das Routing ein:
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); Das wäre der erste Ansatz. Was passiert nun, wenn zwischen main.product.index und main.product.details ? Der Inhalt und das Widget werden neu geladen, aber wir möchten nur den Inhalt neu laden. Dies war problematisch, und Entwickler haben tatsächlich Router entwickelt, die genau diese Funktionalität unterstützen würden. Einer der Namen dafür war Sticky Views . Glücklicherweise unterstützt ui-router dies standardmäßig mit absolutem Named-View-Targeting .
// 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' } } });Indem wir die Zustandsdefinition in die übergeordnete Ansicht verschieben, die ebenfalls abstrakt ist, können wir verhindern, dass die untergeordnete Ansicht beim Wechseln von URLs neu geladen wird, was sich normalerweise auf die Geschwister dieses untergeordneten Elements auswirkt. Natürlich könnte das Widget eine einfache Anweisung sein. Aber der Punkt ist, dass es auch ein anderer komplexer verschachtelter Zustand sein könnte.
Es gibt eine andere Möglichkeit, dies durch die Verwendung von $urlRouterProvider.deferIntercept() zu tun, aber ich denke, dass die Verwendung der Zustandskonfiguration tatsächlich besser ist. Wenn Sie sich für das Abfangen von Routen interessieren, habe ich ein kleines Tutorial zu StackOverflow geschrieben.
Häufiger Fehler Nr. 6: Alles in der Angular-Welt mit anonymen Funktionen deklarieren
Dieser Fehler ist von leichterem Kaliber und eher eine Frage des Stils als das Vermeiden von AngularJS-Fehlermeldungen. Sie haben vielleicht schon bemerkt, dass ich selten anonyme Funktionen an die Deklarationen von angle internal übergebe. Normalerweise definiere ich zuerst eine Funktion und übergebe sie dann.
Dies betrifft mehr als nur Funktionen. Ich habe diesen Ansatz durch das Lesen von Styleguides erhalten, insbesondere von Airbnb und Todd Motto. Ich glaube, es gibt mehrere Vorteile und fast keine Nachteile.
Zunächst einmal können Sie Ihre Funktionen und Objekte viel einfacher manipulieren und mutieren, wenn sie einer Variablen zugewiesen sind. Zweitens ist der Code sauberer und kann leicht in Dateien aufgeteilt werden. Das bedeutet Wartbarkeit. Wenn Sie den globalen Namensraum nicht verunreinigen wollen, verpacken Sie jede Datei in IIFEs. Der dritte Grund ist die Testbarkeit. Betrachten Sie dieses Beispiel:
'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); Jetzt könnten wir also die publicMethod1 verspotten, aber warum sollten wir das tun, da sie offengelegt ist? Wäre es nicht einfacher, die bestehende Methode einfach auszuspionieren? Die Methode ist jedoch eigentlich eine andere Funktion - ein dünner Wrapper. Sehen Sie sich diesen Ansatz an:
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; }Dabei geht es nicht nur um Stil, da der Code tatsächlich wiederverwendbarer und idiomatischer ist. Der Entwickler erhält mehr Ausdruckskraft. Das Aufteilen des gesamten Codes in in sich geschlossene Blöcke macht es nur einfacher.
Häufiger Fehler Nr. 7: Schwere Verarbeitung in eckigem AKA mit Arbeitern
In einigen Szenarien kann es erforderlich sein, ein großes Array komplexer Objekte zu verarbeiten, indem sie durch eine Reihe von Filtern, Decorators und schließlich einen Sortieralgorithmus geleitet werden. Ein Anwendungsfall ist, wenn die App offline funktionieren soll oder wenn die Leistung beim Anzeigen von Daten entscheidend ist. Und da JavaScript Single-Threaded ist, ist es relativ einfach, den Browser einzufrieren.
Es ist auch einfach, es mit Webworkern zu vermeiden. Es scheint keine populären Bibliotheken zu geben, die dies speziell für AngularJS handhaben. Es könnte jedoch das Beste sein, da die Implementierung einfach ist.
Zuerst richten wir den Dienst ein:
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);Nun der Arbeiter:
'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); Fügen Sie nun den Dienst wie gewohnt ein und behandeln scoringService.scoreItems() wie jede andere Dienstmethode, die ein Promise zurückgibt. Die umfangreiche Verarbeitung wird in einem separaten Thread ausgeführt, und der UX wird kein Schaden zugefügt.
Worauf Sie achten sollten:
- Es scheint keine allgemeine Regel dafür zu geben, wie viele Arbeiter gespawnt werden. Einige Entwickler behaupten, dass 8 eine gute Zahl ist, aber verwenden Sie einen Online-Rechner und passen Sie sich an
- Überprüfen Sie die Kompatibilität mit älteren Browsern
- Ich stoße auf ein Problem, wenn ich die Nummer 0 vom Dienst an den Arbeiter übergebe. Ich habe
.toString()auf die übergebene Eigenschaft angewendet und es hat korrekt funktioniert.
Häufiger Fehler Nr. 8: Überbeanspruchung und Missverständnisse lösen
Auflösungen verlängern das Laden der Ansicht um zusätzliche Zeit. Ich glaube, dass eine hohe Leistung der Frontend-App unser primäres Ziel ist. Es sollte kein Problem sein, einige Teile der Ansicht zu rendern, während die App auf die Daten von der API wartet.
Betrachten Sie dieses Setup:
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) }); }Die Konsolenausgabe wird sein:
Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executedWas im Grunde bedeutet:
- Die Auflösungen werden asynchron ausgeführt
- Wir können uns nicht auf eine Ausführungsreihenfolge verlassen (oder müssen uns zumindest ziemlich beugen)
- Alle Zustände werden blockiert, bis alle Resolutionen ihr Ding machen, selbst wenn sie nicht abstrakt sind.
Das bedeutet, dass der Benutzer, bevor er eine Ausgabe sieht, auf alle Abhängigkeiten warten muss. Wir brauchen diese Daten, sicher, okay. Wenn es absolut notwendig ist, es vor der Ansicht zu haben, fügen Sie es in einen .run() -Block ein. Rufen Sie andernfalls den Dienst einfach vom Controller aus auf und behandeln Sie den halb geladenen Zustand ordnungsgemäß. Es ist besser, die laufenden Arbeiten zu sehen – und der Controller wird bereits ausgeführt, also ist er tatsächlich im Gange –, als die App zum Stillstand zu bringen.
Häufiger Fehler Nr. 9: Die App nicht optimieren – drei Beispiele
a) Verursacht zu viele Digest-Loops, wie z. B. das Anbringen von Schiebereglern an Modellen
Dies ist ein allgemeines Problem, das zu AngularJS-Fehlern führen kann, aber ich werde es am Beispiel von Schiebereglern diskutieren. Ich habe diese Schiebereglerbibliothek, Winkelbereichsschieberegler, verwendet, weil ich die erweiterte Funktionalität benötigte. Diese Direktive hat diese Syntax in der Minimalversion:
<body ng-controller="MainController as MC"> <div range-slider min="0" max="MC.maxPrice" pin-handle="min" model-max="MC.price" > </div> </body>Betrachten Sie den folgenden Code im Controller:
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'); } } });Das funktioniert also langsam. Die lässige Lösung wäre, ein Timeout für die Eingabe festzulegen. Das ist aber nicht immer praktisch, und manchmal will man den eigentlichen Modellwechsel auch nicht unbedingt hinauszögern.
Wir fügen also ein temporäres Modell hinzu, das das Arbeitsmodell bei Zeitüberschreitung ändern soll:
<body ng-controller="MainController as MC"> <div range-slider min="0" max="MC.maxPrice" pin-handle="min" model-max="MC.priceTemporary" > </div> </body>und im Steuergerät:
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) Keine Verwendung von $applyAsync

AngularJS hat keinen Polling-Mechanismus zum Aufrufen $digest() . Es wird nur ausgeführt, weil wir die Direktiven (z. B. ng-click , input ), Dienste ( $timeout , $http ) und Methoden ( $watch ) verwenden, die unseren Code auswerten und anschließend einen Digest aufrufen.
.$applyAsync() verzögert die Auflösung von Ausdrücken bis zum nächsten $digest() -Zyklus, der nach einem Timeout von 0 ausgelöst wird, was tatsächlich ~10 ms beträgt.
Es gibt jetzt zwei Möglichkeiten, applyAsync zu verwenden. Ein automatisierter Weg für $http Anfragen und ein manueller Weg für den Rest.
Um alle HTTP-Anforderungen, die etwa zur gleichen Zeit zurückkommen, in einem Digest aufzulösen, gehen Sie wie folgt vor:
mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); }); Der manuelle Weg zeigt, wie es tatsächlich funktioniert. Stellen Sie sich eine Funktion vor, die beim Rückruf an einen Vanilla-JS-Ereignis-Listener oder eine jQuery .click() oder eine andere externe Bibliothek ausgeführt wird. Nachdem es ausgeführt und Modelle geändert hat, müssen Sie $scope.$root.$digest() ( $rootScope.$digest() ) oder zumindest $scope.$digest() aufrufen, wenn Sie es nicht bereits in ein $apply() eingeschlossen haben $scope.$digest() . Andernfalls sehen Sie keine Änderung.
Wenn Sie dies mehrmals in einem Flow tun, wird es möglicherweise langsam ausgeführt. Erwägen Sie stattdessen den Aufruf $scope.$applyAsync() für die Ausdrücke. Es wird nur ein Digest-Zyklus für alle eingestellt.
c) Schwere Verarbeitung von Bildern
Wenn Sie eine schlechte Leistung feststellen, können Sie den Grund mithilfe der Zeitleiste in den Chrome Developer Tools untersuchen. Ich werde mehr über dieses Tool in Fehler #17 schreiben. Wenn Ihr Zeitachsendiagramm nach der Aufnahme von der Farbe Grün dominiert wird, können Ihre Leistungsprobleme mit der Verarbeitung von Bildern zusammenhängen. Dies hängt nicht unbedingt mit AngularJS zusammen, kann aber zusätzlich zu AngularJS-Leistungsproblemen auftreten (die in der Grafik meistens gelb sein würden). Als Front-End-Ingenieure müssen wir über das komplette Endprojekt nachdenken.
Nehmen Sie sich einen Moment Zeit, um zu beurteilen:
- Benutzt du Parallaxe?
- Haben Sie mehrere Content-Ebenen, die sich überlagern?
- Verschieben Sie Ihre Bilder?
- Skalieren Sie Bilder (zB mit Hintergrundgröße)?
- Ändern Sie die Größe von Bildern in Schleifen und verursachen Sie möglicherweise Digest-Loops bei der Größenänderung?
Wenn Sie mindestens drei der oben genannten Punkte mit „Ja“ beantwortet haben, ziehen Sie eine Lockerung in Betracht. Vielleicht können Sie verschiedene Bildgrößen bedienen und die Größe überhaupt nicht ändern. Vielleicht könnten Sie den „transform: translateZ(0)“ Force-GPU-Verarbeitungs-Hack hinzufügen. Oder verwenden Sie requestAnimationFrame für Handler.
Häufiger Fehler Nr. 10: jQuerying It – Detached DOM Tree
Sie hören wahrscheinlich oft, dass es nicht empfohlen wird, jQuery mit AngularJS zu verwenden, und dass dies vermieden werden sollte. Es ist zwingend erforderlich, den Grund hinter diesen Aussagen zu verstehen. Soweit ich sehen kann, gibt es dafür mindestens drei Gründe, aber keiner davon ist ein wirklicher Blocker.
Grund 1: Wenn Sie jQuery-Code ausführen, müssen Sie $digest() selbst aufrufen. Für viele Fälle gibt es eine AngularJS-Lösung, die auf AngularJS zugeschnitten ist und innerhalb von Angular besser genutzt werden kann als jQuery (zB ng-click oder das Event-System).
Grund 2: Die Denkweise zum Erstellen der App. Wenn Sie Websites JavaScript hinzufügen, die beim Navigieren neu laden, müssen Sie sich keine allzu großen Gedanken über den Speicherverbrauch machen. Bei Single-Page-Apps müssen Sie sich Sorgen machen. Wenn Sie nicht aufräumen, können bei Benutzern, die mehr als ein paar Minuten mit Ihrer App verbringen, zunehmend Leistungsprobleme auftreten.
Grund 3: Aufräumen ist eigentlich nicht die einfachste Sache zu tun und zu analysieren. Es gibt keine Möglichkeit, einen Garbage Collector aus dem Skript (im Browser) aufzurufen. Sie können mit abgelösten DOM-Bäumen enden. Ich habe ein Beispiel erstellt (jQuery wird in index.html geladen):
<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);Dies ist eine einfache Anweisung, die Text ausgibt. Darunter befindet sich eine Schaltfläche, die die Direktive einfach manuell zerstört.
Wenn die Direktive also entfernt wird, bleibt ein Verweis auf den DOM-Baum in scope.toBeDetached. Wenn Sie in den Chrome-Entwicklungstools auf die Registerkarte „Profile“ und dann auf „Heap-Snapshot erstellen“ zugreifen, sehen Sie in der Ausgabe:
Sie können mit wenigen leben, aber es ist schlecht, wenn Sie eine Tonne haben. Vor allem, wenn Sie es aus irgendeinem Grund, wie im Beispiel, auf dem Oszilloskop speichern. Das gesamte DOM wird bei jedem Digest ausgewertet. Der problematische getrennte DOM-Baum ist derjenige mit 4 Knoten. Wie kann das also gelöst werden?
scope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });Der freistehende DOM-Baum mit 4 Einträgen wird entfernt!
In diesem Beispiel verwendet die Direktive denselben Geltungsbereich und speichert das DOM-Element im Geltungsbereich. Es war einfacher für mich, es so zu demonstrieren. Es wird nicht immer so schlimm, da Sie es in einer Variablen speichern könnten. Es würde jedoch immer noch Speicherplatz beanspruchen, wenn eine Closure, die auf diese Variable oder eine andere aus demselben Funktionsbereich verwiesen hatte, weiterlebte.
Häufiger Fehler Nr. 11: Überbeanspruchung des isolierten Bereichs
Wann immer Sie eine Anweisung benötigen, von der Sie wissen, dass sie an einem einzigen Ort verwendet wird oder von der Sie nicht erwarten, dass sie mit der Umgebung kollidiert, in der sie verwendet wird, müssen Sie keinen isolierten Bereich verwenden. In letzter Zeit gibt es einen Trend, wiederverwendbare Komponenten zu erstellen, aber wussten Sie, dass eckige Kerndirektiven überhaupt keinen isolierten Bereich verwenden?
Dafür gibt es zwei Hauptgründe: Sie können nicht zwei isolierte Geltungsbereichsdirektiven auf ein Element anwenden, und es können Probleme mit der Verschachtelung/Vererbung/Ereignisverarbeitung auftreten. Besonders in Bezug auf Transklusion - die Auswirkungen sind möglicherweise nicht so, wie Sie es erwarten.
Das würde also scheitern:
<p isolated-scope-directive another-isolated-scope-directive ng-if="MC.quux" ng-repeat="q in MC.quux"></p>Und selbst wenn Sie nur eine Direktive verwenden, werden Sie feststellen, dass weder die isolierten Bereichsmodelle noch die in isolatedScopeDirective übertragenen Ereignisse für AnotherController verfügbar sind. Das ist traurig, Sie können sich beugen und Transklusionsmagie verwenden, damit es funktioniert - aber für die meisten Anwendungsfälle besteht keine Notwendigkeit, zu isolieren.
<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>Also jetzt zwei Fragen:
- Wie können Sie Parent-Scope-Modelle in einer Scope-Direktive verarbeiten?
- Wie können Sie neue Modellwerte instanziieren?
Es gibt zwei Möglichkeiten, bei beiden übergeben Sie Werte an Attribute. Betrachten Sie diesen MainController:
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]; }Das steuert diese Ansicht:
<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>Beachten Sie, dass „watch-attribute“ nicht interpoliert wird. Es funktioniert alles dank JS-Magie. Hier ist die Richtliniendefinition:
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' }; } Beachten Sie, dass attrs.watchAttribute ohne die Anführungszeichen an scope.$watch() übergeben wird! Das heißt, was tatsächlich an $watch übergeben wurde, war die Zeichenfolge MC.foo ! Es funktioniert jedoch, da jede an $watch() übergebene Zeichenfolge anhand des Bereichs ausgewertet wird und MC.foo im Bereich verfügbar ist. Dies ist auch die häufigste Art und Weise, wie Attribute in AngularJS-Kerndirektiven überwacht werden.
Sehen Sie sich den Code auf Github für die Vorlage an und sehen Sie sich $parse und $eval an, um noch mehr zu sehen.
Häufiger Fehler Nr. 12: Nicht hinter sich selbst aufräumen – Beobachter, Intervalle, Zeitüberschreitungen und Variablen
AngularJS erledigt einige Arbeiten für Sie, aber nicht alle. Folgendes muss manuell bereinigt werden:
- Alle Beobachter, die nicht an den aktuellen Geltungsbereich gebunden sind (z. B. an $rootScope gebunden)
- Intervalle
- Zeitüberschreitungen
- Variablen, die in Direktiven auf DOM verweisen
- Zwielichtige jQuery-Plugins, zB solche, die keine Handler haben, die auf das JavaScript-Ereignis
$destroyreagieren
Wenn Sie dies nicht manuell tun, werden Sie auf unerwartetes Verhalten und Speicherlecks stoßen. Noch schlimmer – diese werden nicht sofort sichtbar sein, aber sie werden sich irgendwann einschleichen. Murphys Gesetz.
Erstaunlicherweise bietet AngularJS praktische Möglichkeiten, um mit all diesen Dingen umzugehen:
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. }); }; Beachten Sie das jQuery $destroy Ereignis. Es heißt wie das von AngularJS, wird aber separat behandelt. Scope $watchers reagiert nicht auf das jQuery-Event.
Häufiger Fehler Nr. 13: Zu viele Beobachter haben
Das sollte jetzt ganz einfach sein. Hier gibt es eine Sache zu verstehen: $digest() . Für jede Bindung {{ model }} erstellt AngularJS einen Watcher. In jeder Verdauungsphase wird jede solche Bindung bewertet und mit dem vorherigen Wert verglichen. Das nennt man Dirty-Checking, und genau das macht $digest. Wenn sich der Wert seit der letzten Überprüfung geändert hat, wird der Watcher-Callback ausgelöst. 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().$rootor 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.
Starten Sie zunächst Ihren Webserver auf Host 0.0.0.0, damit er von Ihrem lokalen Netzwerk aus erreichbar ist. Aktivieren Sie den Webinspektor in den Einstellungen. Verbinden Sie dann Ihr Gerät mit Ihrem Desktop und greifen Sie auf Ihre lokale Entwicklungsseite zu, indem Sie die IP Ihres Computers anstelle des regulären „localhost“ verwenden. Das ist alles, was Sie brauchen, Ihr Gerät sollte Ihnen jetzt über den Browser Ihres Desktops zur Verfügung stehen.
Hier sind die detaillierten Anleitungen für Android. Und für iOS sind inoffizielle Anleitungen leicht über Google zu finden.
Ich hatte kürzlich einige coole Erfahrungen mit browserSync. Es funktioniert ähnlich wie Livereload, synchronisiert aber auch alle Browser, die dieselbe Seite über browserSync anzeigen. Dazu gehören Benutzerinteraktionen wie Scrollen, Klicken auf Schaltflächen usw. Ich habe mir die Protokollausgabe der iOS-App angesehen, während ich die Seite auf dem iPad von meinem Desktop aus gesteuert habe. Es hat gut funktioniert!
Häufiger Fehler Nr. 18: Den Quellcode des NG-INIT-Beispiels nicht lesen
Ng-init sollte dem Klang nach ähnlich sein wie ng-if und ng-repeat , richtig? Haben Sie sich jemals gefragt, warum es in den Dokumenten einen Kommentar gibt, dass es nicht verwendet werden sollte? IMHO war das überraschend! Ich würde erwarten, dass die Direktive ein Modell initialisiert. Das tut es auch, aber… es ist anders implementiert, das heißt, es überwacht nicht den Attributwert. Sie müssen den AngularJS-Quellcode nicht durchsuchen - ich bringe ihn Ihnen:
var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });Weniger als erwartet? Ziemlich lesbar, abgesehen von der umständlichen Direktivensyntax, nicht wahr? Um die sechste Zeile geht es.
Vergleichen Sie es mit ng-show:
var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }]; Wieder die sechste Zeile. Dort gibt es ein $watch , das macht diese Direktive dynamisch. Im AngularJS-Quellcode besteht ein großer Teil des gesamten Codes aus Kommentaren, die Code beschreiben, der von Anfang an größtenteils lesbar war. Ich glaube, es ist eine großartige Möglichkeit, etwas über AngularJS zu lernen.
Fazit
Dieser Leitfaden, der die häufigsten AngularJS-Fehler abdeckt, ist fast doppelt so lang wie die anderen Leitfäden. Das hat sich natürlich so ergeben. Die Nachfrage nach qualitativ hochwertigen JavaScript-Frontend-Ingenieuren ist sehr hoch. AngularJS ist im Moment so heiß und hält seit einigen Jahren eine stabile Position unter den beliebtesten Entwicklungstools. Mit AngularJS 2.0 auf dem Weg wird es wahrscheinlich für die kommenden Jahre dominieren.
Das Tolle an der Front-End-Entwicklung ist, dass sie sehr lohnend ist. Unsere Arbeit ist sofort sichtbar und die Menschen interagieren direkt mit den von uns gelieferten Produkten. Die Zeit, die wir damit verbringen, JavaScript zu lernen, und ich glaube, wir sollten uns auf die JavaScript-Sprache konzentrieren, ist eine sehr gute Investition. Es ist die Sprache des Internets. Die Konkurrenz ist super stark! Für uns gibt es einen Fokus - Benutzererfahrung. Um erfolgreich zu sein, müssen wir alles abdecken.
Der in diesen Beispielen verwendete Quellcode kann von GitHub heruntergeladen werden. Fühlen Sie sich frei, es herunterzuladen und zu Ihrem eigenen zu machen.
Ich wollte vier Publishing-Entwicklern Anerkennung zollen, die mich am meisten inspiriert haben:
- Ben Nadel
- Todd Motto
- Paskal Precht
- Sandeep-Panda
Ich möchte auch all den großartigen Leuten auf FreeNode #angularjs- und #javascript-Kanälen für viele ausgezeichnete Gespräche und kontinuierliche Unterstützung danken.
Und zum Schluss immer daran denken:
// when in doubt, comment it out! :) 