Οι εφαρμογές μιας σελίδας απαιτούν από τους προγραμματιστές front-end να γίνουν καλύτεροι μηχανικοί λογισμικού. Το CSS και το HTML δεν αποτελούν πλέον τη μεγαλύτερη ανησυχία, στην πραγματικότητα, δεν υπάρχει πλέον μόνο μία ανησυχία. Ο προγραμματιστής front-end πρέπει να χειρίζεται XHRs, λογική εφαρμογών (μοντέλα, προβολές, ελεγκτές), απόδοση, κινούμενα σχέδια, στυλ, δομή, SEO και ενσωμάτωση με εξωτερικές υπηρεσίες. Το αποτέλεσμα που προκύπτει από όλους αυτούς τους συνδυασμούς είναι η Εμπειρία χρήστη (UX) που πρέπει πάντα να έχει προτεραιότητα.
Το AngularJS είναι ένα πολύ ισχυρό πλαίσιο. Είναι το τρίτο αποθετήριο με τα περισσότερα αστέρια στο GitHub. Δεν είναι δύσκολο να αρχίσετε να χρησιμοποιείτε, αλλά οι στόχοι που προορίζεται να επιτύχει την κατανόηση της ζήτησης. Δεν μπορούν πλέον οι προγραμματιστές της AngularJS να αγνοήσουν την κατανάλωση μνήμης, επειδή δεν θα επαναφερθούν πλέον στην πλοήγηση. Αυτή είναι η εμπροσθοφυλακή του Ανάπτυξη διαδικτύου . Ας το αγκαλιάσουμε!
Συνιστώνται μερικές τροποποιήσεις βελτιστοποίησης για παραγωγή. Ένα από αυτά είναι η απενεργοποίηση των πληροφοριών εντοπισμού σφαλμάτων.
DebugInfoEnabled
είναι μια ρύθμιση που από προεπιλογή είναι αληθής και επιτρέπει την πρόσβαση στο εύρος μέσω κόμβων DOM. Εάν θέλετε να το δοκιμάσετε μέσω της κονσόλας JavaScript, επιλέξτε ένα στοιχείο DOM και αποκτήστε πρόσβαση στο πεδίο εφαρμογής του με:
angular.element(document.body).scope()
Μπορεί να είναι χρήσιμο ακόμη και όταν δεν το χρησιμοποιείτε jQuery με το CSS του, αλλά δεν πρέπει να χρησιμοποιείται εκτός της κονσόλας. Ο λόγος είναι ότι όταν $compileProvider.debugInfoEnabled
έχει οριστεί σε false, κλήση .scope()
σε έναν κόμβο DOM θα επιστρέψει undefined
.
Αυτή είναι μία από τις λίγες προτεινόμενες επιλογές παραγωγής.
Λάβετε υπόψη ότι εξακολουθείτε να έχετε πρόσβαση στο πεδίο εφαρμογής μέσω της κονσόλας, ακόμα και όταν είστε σε παραγωγή. Κλήση angular.reloadWithDebugInfo()
από την κονσόλα και η εφαρμογή θα κάνει ακριβώς αυτό.
Ίσως το έχετε διαβάσει αν ήσασταν δεν έχει τελεία στο μοντέλο ng , το κάνατε λάθος. Όσον αφορά την κληρονομιά, αυτή η δήλωση είναι συχνά αληθινή. Τα πεδία έχουν ένα πρωτότυπο μοντέλο κληρονομιάς, τυπικό στη JavaScript και τα ένθετα πεδία είναι κοινά για το AngularJS. Πολλές οδηγίες δημιουργούν παιδικά πεδία όπως ngRepeat
, ngIf
, και ngController
. Κατά την επίλυση ενός μοντέλου, η αναζήτηση ξεκινά από το τρέχον εύρος και περνά από κάθε γονικό εύρος, μέχρι το $rootScope
.
Όμως, όταν ορίζετε μια νέα τιμή, αυτό που συμβαίνει εξαρτάται από το είδος του μοντέλου (μεταβλητή) που θέλουμε να αλλάξουμε. Εάν το μοντέλο είναι πρωτόγονο, το πεδίο εφαρμογής για παιδιά θα δημιουργήσει ένα νέο μοντέλο. Αλλά αν η αλλαγή αφορά μια ιδιότητα ενός αντικειμένου μοντέλου, η αναζήτηση στα γονικά πεδία θα βρει το αντικείμενο αναφοράς και θα αλλάξει την πραγματική του ιδιότητα. Ένα νέο μοντέλο δεν θα οριστεί στο τρέχον εύρος, οπότε δεν θα γίνει κάλυψη:
function MainController($scope) { $scope.foo = 1; $scope.bar = {innerProperty: 2}; } angular.module('myApp', []) .controller('MainController', MainController);
OUTER SCOPE:
{{ foo }}
{{ bar.innerProperty }}
INNER SCOPE
{{ foo }}
{{ bar.innerProperty }}
Set primitive Mutate object
Κάνοντας κλικ στο κουμπί με την ένδειξη 'Set primitive' θα ορίσετε το foo στο εσωτερικό πεδίο σε 2, αλλά δεν θα αλλάξει foo στο εξωτερικό πεδίο.
Κάνοντας κλικ στο κουμπί 'Αλλαγή αντικειμένου' θα αλλάξει η ιδιότητα της γραμμής από το γονικό πεδίο. Δεδομένου ότι δεν υπάρχει μεταβλητή στο εσωτερικό πεδίο, δεν θα υπάρξει σκίαση και η ορατή τιμή για τη γραμμή θα είναι 3 και στα δύο πεδία.
Ένας άλλος τρόπος για να το κάνετε αυτό είναι να αξιοποιήσετε το γεγονός ότι τα γονικά πεδία και το ριζικό πεδίο αναφοράς αναφέρονται από κάθε πεδίο. Το $parent
και $root
αντικείμενα μπορούν να χρησιμοποιηθούν για πρόσβαση στο γονικό εύρος και $rootScope
, αντίστοιχα, απευθείας από την προβολή. Μπορεί να είναι ένας ισχυρός τρόπος, αλλά δεν είμαι οπαδός του λόγω του προβλήματος με τη στόχευση ενός συγκεκριμένου πεδίου στο ρεύμα. Υπάρχει ένας άλλος τρόπος για να ορίσετε και να αποκτήσετε πρόσβαση σε συγκεκριμένες ιδιότητες για ένα εύρος - χρησιμοποιώντας το controllerAs
σύνταξη.
Ο εναλλακτικός και αποτελεσματικότερος τρόπος για να αντιστοιχίσετε μοντέλα για χρήση αντικειμένου ελεγκτή αντί για το πεδίο εμβολιασμού $. Αντί να εισάγουμε πεδίο εφαρμογής, μπορούμε να ορίσουμε μοντέλα ως εξής:
function MainController($scope) { this.foo = 1; var that = this; var setBar = function () { // that.bar = {someProperty: 2}; this.bar = {someProperty: 2}; }; setBar.call(this); // there are other conventions: // var MC = this; // setBar.call(this); when using 'this' inside setBar() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
Αυτό είναι πολύ λιγότερο συγκεχυμένο. Ειδικά όταν υπάρχουν πολλά ένθετα πεδία, όπως μπορεί να συμβαίνει με τις ένθετες καταστάσεις.
Υπάρχουν περισσότερα για τη σύνταξη του ελεγκτή.
Υπάρχουν μερικές προειδοποιήσεις σχετικά με το πώς εκτίθεται το αντικείμενο του ελεγκτή. Είναι βασικά ένα αντικείμενο που τίθεται στο πεδίο εφαρμογής του ελεγκτή, ακριβώς όπως ένα κανονικό μοντέλο.
Εάν πρέπει να παρακολουθήσετε μια ιδιότητα του αντικειμένου ελεγκτή, μπορείτε να παρακολουθήσετε μια συνάρτηση, αλλά δεν απαιτείται. Εδώ είναι ένα παράδειγμα:
function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }
Είναι πιο εύκολο να κάνετε:
function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }
Αυτό σημαίνει επίσης ότι κάτω από την αλυσίδα πεδίου, θα μπορούσατε να έχετε πρόσβαση στο MC από έναν θυγατρικό ελεγκτή:
function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }
Ωστόσο, για να μπορέσετε να το κάνετε αυτό πρέπει να είστε συνεπείς με το ακρωνύμιο που χρησιμοποιείτε για τον ελεγκτήA. Υπάρχουν τουλάχιστον τρεις τρόποι για να το ρυθμίσετε. Είδατε ήδη το πρώτο:
…
Ωστόσο, εάν χρησιμοποιείτε ui-router
, ορίζοντας έναν ελεγκτή με αυτόν τον τρόπο είναι επιρρεπές σε λάθη. Για καταστάσεις, οι ελεγκτές πρέπει να καθορίζονται στη διαμόρφωση κατάστασης:
angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });
Υπάρχει ένας άλλος τρόπος σχολιασμού:
(…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })
Μπορείτε να κάνετε το ίδιο στις οδηγίες:
Υπολογιστής ποσοστού μισθού σε σύμβαση
function AnotherController() { this.text = 'abc'; } function testForApeeScape() { return { controller: 'AnotherController as AC', template: '{{ AC.text }}
' }; } angular.module('myApp', []) .controller('AnotherController', AnotherController) .directive('testForApeeScape', testForApeeScape);
Ο άλλος τρόπος σχολιασμού ισχύει επίσης, αν και λιγότερο συνοπτικός:
function testForApeeScape() { return { controller: 'AnotherController', controllerAs: 'AC', template: '{{ AC.text }}
' }; }
Η de facto λύση δρομολόγησης για το AngularJS ήταν, μέχρι τώρα, η ui-router
. Καταργήθηκε από τον πυρήνα πριν από λίγο, το ngRoute module, ήταν πολύ βασικό για πιο εξελιγμένη δρομολόγηση.
Υπάρχει ένα νέο NgRouter
στο δρόμο, αλλά οι συγγραφείς το θεωρούν πολύ νωρίς για παραγωγή. Όταν το γράφω αυτό, η σταθερή γωνιακή τιμή είναι 1.3.15 και ui-router
πετρώματα.
Οι κύριοι λόγοι:
Εδώ θα καλύψω κατάσταση ένθεσης για την αποφυγή σφαλμάτων AngularJS.
Σκεφτείτε το ως μια περίπλοκη αλλά τυπική περίπτωση χρήσης. Υπάρχει μια εφαρμογή, η οποία έχει προβολή αρχικής σελίδας και προβολή προϊόντος. Η προβολή προϊόντος έχει τρεις ξεχωριστές ενότητες: την εισαγωγή, το widget και το περιεχόμενο. Θέλουμε το widget να παραμένει και να μην φορτώνεται ξανά κατά την εναλλαγή μεταξύ της κατάστασης. Αλλά το περιεχόμενο πρέπει να φορτωθεί ξανά.
Εξετάστε την ακόλουθη δομή σελίδας ευρετηρίου προϊόντων HTML:
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
Αυτό είναι κάτι που θα μπορούσαμε να πάρουμε από τον κωδικοποιητή HTML και τώρα πρέπει να τον χωρίσουμε σε αρχεία και καταστάσεις. Γενικά συμφωνώ με τη σύμβαση ότι υπάρχει μια αφηρημένη ΚΥΡΙΑ κατάσταση, η οποία διατηρεί τα παγκόσμια δεδομένα, εάν χρειάζεται. Χρησιμοποιήστε αυτό αντί για $ rootScope. ο Κύριος Η κατάσταση θα διατηρήσει επίσης στατικό HTML που απαιτείται σε κάθε σελίδα. Κρατώ το index.html καθαρό.
function config($stateProvider) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { abstract: true, url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html' }) // A SIMPLE HOMEPAGE .state('main.homepage', { url: '', controller: 'HomepageController as HC', templateUrl: '/routing-demo/homepage.html' }) // THE ABOVE IS ALL GOOD, HERE IS TROUBLE // A COMPLEX PRODUCT PAGE .state('main.product', { abstract: true, url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }); } angular.module('articleApp', [ 'ui.router' ]) .config(config);
main.product.index
Στη συνέχεια, ας δούμε τη σελίδα ευρετηρίου προϊόντων:
main.product.details
Όπως μπορείτε να δείτε, η σελίδα ευρετηρίου προϊόντων έχει τρεις προβολές με όνομα. Ένα για την εισαγωγή, ένα για το widget και ένα για το προϊόν. Γνωρίζουμε τις προδιαγραφές! Ας ρυθμίσουμε λοιπόν τη δρομολόγηση:
ui-router
Αυτή θα ήταν η πρώτη προσέγγιση. Τώρα, τι συμβαίνει κατά την εναλλαγή μεταξύ // A COMPLEX PRODUCT PAGE // WITH NO MORE TROUBLE .state('main.product', { abstract: true, url: ':id', views: { // TARGETING THE UNNAMED VIEW IN MAIN.HTML '@main': { controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html' }, // TARGETING THE WIDGET VIEW IN PRODUCT.HTML // BY DEFINING A CHILD VIEW ALREADY HERE, WE ENSURE IT DOES NOT RELOAD ON CHILD STATE CHANGE ' [email protected] ': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' } } }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } });
και $urlRouterProvider.deferIntercept()
; Το περιεχόμενο και το widget φορτώνονται ξανά, αλλά θέλουμε μόνο να φορτώσουμε ξανά το περιεχόμενο. Αυτό ήταν προβληματικό και οι προγραμματιστές δημιούργησαν πραγματικά δρομολογητές που θα υποστήριζαν μόνο αυτήν τη λειτουργία. Ένα από τα ονόματα για αυτό ήταν κολλώδη θέα . Ευτυχώς, 'use strict'; function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // THE BELOW CALL CANNOT BE SPIED ON WITH JASMINE publicMethod1('someArgument'); }; // IF THE LITERAL IS RETURNED THIS WAY, IT CAN'T BE REFERRED TO FROM INSIDE return { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; } angular.module('app', []) .factory('yoda', yoda);
υποστηρίζει αυτό έξω από το κουτί με απόλυτη στόχευση προβολής .
publicMethod1
Μετακινώντας τον ορισμό της κατάστασης στην προβολή γονέα, η οποία είναι επίσης αφηρημένη, μπορούμε να διατηρήσουμε την προβολή του παιδιού από την επαναφόρτωση κατά την εναλλαγή διευθύνσεων URL που επηρεάζουν κανονικά τα αδέλφια αυτού του παιδιού. Φυσικά, το widget θα μπορούσε να είναι μια απλή οδηγία. Αλλά το θέμα είναι, θα μπορούσε επίσης να είναι μια άλλη πολύπλοκη ένθετη κατάσταση.
Υπάρχει ένας άλλος τρόπος για να το κάνετε αυτό μέσω της χρήσης function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // the below call cannot be spied on publicMethod1('someArgument'); // BUT THIS ONE CAN! hostObject.publicMethod1('aBetterArgument'); }; var hostObject = { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; return hostObject; }
, αλλά πιστεύω ότι η χρήση της διαμόρφωσης κατάστασης είναι στην πραγματικότητα καλύτερη. Αν ενδιαφέρεστε να παρακολουθήσετε διαδρομές, έγραψα ένα μικρό σεμινάριο Υπερχείλιση στοίβας .
Αυτό λάθος είναι ελαφρύτερου διαμετρήματος και είναι περισσότερο θέμα στυλ από την αποφυγή μηνυμάτων σφάλματος AngularJS. Ίσως έχετε παρατηρήσει στο παρελθόν ότι σπάνια μεταφέρω ανώνυμες συναρτήσεις στις γωνιακές εσωτερικές δηλώσεις. Συνήθως απλώς ορίζω μια συνάρτηση πρώτα και μετά την περνάω.
Αυτό αφορά κάτι περισσότερο από απλές λειτουργίες. Πήρα αυτήν την προσέγγιση από οδηγούς στυλ ανάγνωσης, ειδικά από τους Airbnb και Todd Motto. Πιστεύω ότι υπάρχουν πολλά πλεονεκτήματα και σχεδόν κανένα μειονέκτημα σε αυτό.
Πρώτα απ 'όλα, μπορείτε να χειριστείτε και να μεταλλάξετε τις λειτουργίες και τα αντικείμενά σας πολύ πιο εύκολα εάν έχουν αντιστοιχιστεί σε μια μεταβλητή. Δεύτερον, ο κώδικας είναι καθαρότερος και μπορεί εύκολα να χωριστεί σε αρχεία. Αυτό σημαίνει συντηρησιμότητα. Εάν δεν θέλετε να μολύνετε τον παγκόσμιο χώρο ονομάτων, τυλίξτε κάθε αρχείο σε IIFE. Ο τρίτος λόγος είναι η δυνατότητα δοκιμής. Εξετάστε αυτό το παράδειγμα:
function scoringService($q) { var scoreItems = function (items, weights) { var deferred = $q.defer(); var worker = new Worker('/worker-demo/scoring.worker.js'); var orders = { items: items, weights: weights }; worker.postMessage(orders); worker.onmessage = function (e) { if (e.data && e.data.ready) { deferred.resolve(e.data.items); } }; return deferred.promise; }; var hostObject = { scoreItems: function (items, weights) { return scoreItems(items, weights); } }; return hostObject; } angular.module('app.worker') .factory('scoringService', scoringService);
Τώρα λοιπόν θα μπορούσαμε να χλευάσουμε το 'use strict'; function scoringFunction(items, weights) { var itemsArray = []; for (var i = 0; i b.sum) { return -1; } else if (a.sum
scoringService.scoreItems()
Αυτό δεν αφορά μόνο το στυλ, καθώς στην πραγματικότητα ο κώδικας είναι πιο επαναχρησιμοποιήσιμος και ιδιωματικός. Ο προγραμματιστής αποκτά περισσότερη εκφραστική ισχύ. Ο διαχωρισμός όλων των κωδικών σε αυτόνομα μπλοκ απλώς διευκολύνει.
Σε ορισμένα σενάρια, μπορεί να απαιτείται η επεξεργασία μιας μεγάλης σειράς σύνθετων αντικειμένων περνώντας τα μέσα από ένα σύνολο φίλτρων, διακοσμητών και τέλος ενός αλγορίθμου ταξινόμησης. Μία περίπτωση χρήσης είναι όταν η εφαρμογή πρέπει να λειτουργεί εκτός σύνδεσης ή όπου η απόδοση της εμφάνισης δεδομένων είναι βασική. Και δεδομένου ότι το JavaScript είναι μονόκλωνο, είναι σχετικά εύκολο να παγώσει το πρόγραμμα περιήγησης.
Είναι επίσης εύκολο να το αποφύγετε με τους εργαζόμενους στο Διαδίκτυο. Δεν φαίνεται να υπάρχουν δημοφιλείς βιβλιοθήκες που να το χειρίζονται ειδικά για το AngularJS. Μπορεί όμως να είναι το καλύτερο, καθώς η εφαρμογή είναι εύκολη.
Αρχικά, ας ρυθμίσουμε την υπηρεσία:
.toString()
Τώρα, ο εργαζόμενος:
function resolve(index, timeout) { return { data: function($q, $timeout) { var deferred = $q.defer(); $timeout(function () { deferred.resolve(console.log('Data resolve called ' + index)); }, timeout); return deferred.promise; } }; } function configResolves($stateProvide) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html', resolve: resolve(1, 1597) }) // A COMPLEX PRODUCT PAGE .state('main.product', { url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', resolve: resolve(2, 2584) }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } }, resolve: resolve(3, 987) }); }
Τώρα, εγχύστε την υπηρεσία ως συνήθως και αντιμετωπίστε Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executed
όπως θα κάνατε οποιαδήποτε μέθοδο υπηρεσίας που επιστρέφει μια υπόσχεση. Η βαριά επεξεργασία θα πραγματοποιηθεί σε ξεχωριστό νήμα και δεν θα προκληθεί ζημιά στο UX.
Τι να προσέξετε:
.run()
στην ιδιότητα που πέρασε και λειτούργησε σωστά.Το Resolves προσθέτει επιπλέον χρόνο στη φόρτωση της προβολής. Πιστεύω ότι η υψηλή απόδοση της εφαρμογής front-end είναι ο πρωταρχικός μας στόχος. Δεν πρέπει να αποτελεί πρόβλημα η απόδοση ορισμένων τμημάτων της προβολής ενώ η εφαρμογή περιμένει τα δεδομένα από το API.
Εξετάστε αυτήν τη ρύθμιση:
this.maxPrice = '100'; this.price = '55'; $scope.$watch('MC.price', function (newVal) { if (newVal || newVal === 0) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } });
Η έξοδος της κονσόλας θα είναι:
this.maxPrice = '100'; this.price = '55'; this.priceTemporary = '55'; $scope.$watch('MC.price', function (newVal) { if (!isNaN(newVal)) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } }); var timeoutInstance; $scope.$watch('MC.priceTemporary', function (newVal) { if (!isNaN(newVal)) { if (timeoutInstance) { $timeout.cancel(timeoutInstance); } timeoutInstance = $timeout(function () { $scope.MC.price = newVal; }, 144); } });
Αυτό βασικά σημαίνει ότι:
Αυτό σημαίνει ότι πριν ο χρήστης δει οποιαδήποτε έξοδο, πρέπει να περιμένει όλες τις εξαρτήσεις. Πρέπει να έχουμε αυτά τα δεδομένα, σίγουρα, εντάξει. Εάν είναι απολύτως απαραίτητο να το έχετε πριν από την προβολή, τοποθετήστε το σε ένα $digest()
ΟΙΚΟΔΟΜΙΚΟ ΤΕΤΡΑΓΩΝΟ. Διαφορετικά, απλώς πραγματοποιήστε την κλήση προς την υπηρεσία από τον ελεγκτή και χειριστείτε την κατάσταση μισής φόρτωσης. Βλέποντας την εργασία σε εξέλιξη - και ο ελεγκτής έχει ήδη εκτελεστεί, οπότε στην πραγματικότητα είναι πρόοδος - είναι καλύτερο από το να σταματήσει η εφαρμογή.
α) Προκαλεί πάρα πολλούς βρόχους πέψης, όπως η τοποθέτηση ρυθμιστικών σε μοντέλα
Αυτό είναι ένα γενικό πρόβλημα που μπορεί να οδηγήσει σε σφάλματα AngularJS, αλλά θα το συζητήσω στο παράδειγμα των ρυθμιστικών. Χρησιμοποιούσα αυτήν τη ρυθμιστική βιβλιοθήκη, το ρυθμιστικό γωνιακής εμβέλειας, επειδή χρειαζόμουν την εκτεταμένη λειτουργικότητα. Αυτή η οδηγία έχει αυτήν τη σύνταξη στην ελάχιστη έκδοση:
ng-click
Εξετάστε τον ακόλουθο κώδικα στον ελεγκτή:
input
Λοιπόν αυτό λειτουργεί αργά. Η απλή λύση θα ήταν να ορίσετε ένα χρονικό όριο στην είσοδο. Αλλά αυτό δεν είναι πάντα βολικό και μερικές φορές δεν θέλουμε πραγματικά να καθυστερήσουμε την πραγματική αλλαγή μοντέλου σε όλες τις περιπτώσεις.
Θα προσθέσουμε λοιπόν ένα προσωρινό μοντέλο που θα αλλάξει το λειτουργικό μοντέλο στο χρονικό όριο:
$timeout
και στον ελεγκτή:
$http
β) Μη χρήση του $ applyAsync
Το AngularJS δεν διαθέτει μηχανισμό ψηφοφορίας για κλήση $watch
. Εκτελείται μόνο επειδή χρησιμοποιούμε τις οδηγίες (π.χ. .$applyAsync()
, $digest()
), υπηρεσίες (applyAsync
, $http
) και μεθόδους (mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
) που αξιολογούν τον κωδικό μας και καλέστε ένα χωνευτήρι μετά.
Τι .click()
το κάνει είναι να καθυστερήσει την ανάλυση των εκφράσεων μέχρι την επόμενη $apply()
κύκλος, ο οποίος ενεργοποιείται μετά από ένα χρονικό όριο 0, που στην πραγματικότητα είναι ~ 10ms.
Υπάρχουν δύο τρόποι χρήσης $scope.$root.$digest()
τώρα. Ένας αυτοματοποιημένος τρόπος για $rootScope.$digest()
αιτήματα και έναν χειροκίνητο τρόπο για τα υπόλοιπα.
Για να κάνετε όλα τα αιτήματα http που επιστρέφουν περίπου την ίδια ώρα να επιλυθούν σε μία σύνοψη, κάντε:
$scope.$digest()
Ο χειροκίνητος τρόπος δείχνει πώς λειτουργεί πραγματικά. Εξετάστε κάποια λειτουργία που εκτελείται με την επιστροφή κλήσης σε έναν ακροατή συμβάντων vanilla JS ή ένα jQuery I AM DIRECTIVE$scope.$applyAsync()
ή κάποια άλλη εξωτερική βιβλιοθήκη. Αφού εκτελέσει και αλλάξει μοντέλα, αν δεν το έχετε ήδη τυλίξει σε $digest()
πρέπει να καλέσετε remove directive
(function MainController($rootScope, $scope) { this.removeDirective = function () { $rootScope.$emit('destroyDirective'); }; } function testForApeeScape($rootScope, $timeout) { return { link: function (scope, element, attributes) { var destroyListener = $rootScope.$on('destroyDirective', function () { scope.$destroy(); }); // adding a timeout for the DOM to get ready $timeout(function () { scope.toBeDetached = element.find('p'); }); scope.$on('$destroy', function () { destroyListener(); element.remove(); }); }, template: '
), ή τουλάχιστον scope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });
. Διαφορετικά, δεν θα δείτε καμία αλλαγή.
Εάν το κάνετε πολλές φορές σε μία ροή, ενδέχεται να αρχίσει να λειτουργεί αργά. Εξετάστε το ενδεχόμενο να καλέσετε … the isolated scope is not available here, look: {{ isolatedModel }}
στις εκφράσεις αντ 'αυτού. Θα ορίσει μόνο έναν κύκλο χώνευσης για όλους τους.
γ) Κάνοντας βαριά επεξεργασία εικόνων
Εάν αντιμετωπίζετε κακή απόδοση, μπορείτε να διερευνήσετε τον λόγο χρησιμοποιώντας το Χρονολόγιο από τα Εργαλεία προγραμματιστών Chrome. Θα γράψω περισσότερα για αυτό το εργαλείο κατά λάθος # 17. Εάν το γράφημα χρονοδιάγραμμα κυριαρχείται με το πράσινο χρώμα μετά την εγγραφή, τα ζητήματα απόδοσής σας μπορεί να σχετίζονται με την επεξεργασία εικόνων. Αυτό δεν σχετίζεται αυστηρά με το AngularJS, αλλά μπορεί να συμβεί πάνω από ζητήματα απόδοσης του AngularJS (τα οποία θα ήταν κυρίως κίτρινα στο γράφημα). Ως μηχανικοί front-end, πρέπει να σκεφτούμε το ολοκληρωμένο έργο.
Αφιερώστε λίγο χρόνο για να αξιολογήσετε:
Εάν απαντήσατε «ναι» σε τουλάχιστον τρία από τα παραπάνω, σκεφτείτε να το χαλαρώσετε. Ίσως μπορείτε να προβάλλετε διάφορα μεγέθη εικόνας και να μην αλλάξετε μέγεθος. Ίσως θα μπορούσατε να προσθέσετε το 'transform: translateZ (0)' εξαναγκαστική επεξεργασία επεξεργασίας GPU. Ή χρησιμοποιήστε το requestAnimationFrame για χειριστές.
Πολλές φορές πιθανότατα ακούτε ότι δεν συνιστάται η χρήση του jQuery με το AngularJS και ότι πρέπει να αποφεύγεται. Είναι επιτακτική ανάγκη να κατανοήσουμε τον λόγο πίσω από αυτές τις δηλώσεις. Υπάρχουν τουλάχιστον τρεις λόγοι, κατά τη γνώμη μου, αλλά κανένας από αυτούς δεν είναι πραγματικοί αποκλειστές.
Λόγος 1: Όταν εκτελείτε τον κωδικό jQuery, πρέπει να καλέσετε function MainController($interval) { this.foo = { bar: 1 }; this.baz = 1; var that = this; $interval(function () { that.foo.bar++; }, 144); $interval(function () { that.baz++; }, 144); this.quux = [1,2,3]; }
ο ίδιος. Για πολλές περιπτώσεις, υπάρχει Λύση AngularJS το οποίο είναι προσαρμοσμένο για AngularJS και μπορεί να χρησιμοποιηθεί καλύτερα στο Angular από το jQuery (π.χ. ng-click ή το σύστημα συμβάντων).
Λόγος 2: Η μέθοδος σκέψης για τη δημιουργία της εφαρμογής. Εάν προσθέσατε JavaScript σε ιστότοπους, οι οποίοι φορτώνονται ξανά κατά την πλοήγηση, δεν χρειάζεται να ανησυχείτε για την κατανάλωση μνήμης πάρα πολύ. Με εφαρμογές μιας σελίδας, πρέπει να ανησυχείτε. Εάν δεν κάνετε εκκαθάριση, οι χρήστες που αφιερώνουν περισσότερα από λίγα λεπτά στην εφαρμογή σας ενδέχεται να αντιμετωπίσουν αυξανόμενα προβλήματα απόδοσης.
Λόγος 3: Ο καθαρισμός δεν είναι στην πραγματικότητα το πιο εύκολο πράγμα που πρέπει να κάνετε και να αναλύσετε. Δεν υπάρχει τρόπος να καλέσετε έναν συλλέκτη απορριμάτων από το σενάριο (στο πρόγραμμα περιήγησης). Ενδέχεται να καταλήξετε σε ανεξάρτητα δέντρα DOM. Δημιούργησα ένα παράδειγμα (το jQuery φορτώνεται στο index.html):
function testDirective() { var postLink = function (scope, element, attrs) { scope.$watch(attrs.watchAttribute, function (newVal) { if (newVal) { // take a look in the console // we can't use the attribute directly console.log(attrs.watchAttribute); // the newVal is evaluated, and it can be used scope.modifiedFooBar = newVal.bar * 10; } }, true); attrs.$observe('observeAttribute', function (newVal) { scope.observed = newVal; }); }; return { link: postLink, templateUrl: '/attributes-demo/test-directive.html' }; }
Αυτή είναι μια απλή οδηγία που εξάγει κάποιο κείμενο. Υπάρχει ένα κουμπί κάτω από αυτό, το οποίο θα καταστρέψει την οδηγία χειροκίνητα.
Έτσι, όταν καταργηθεί η οδηγία, παραμένει μια αναφορά στο δέντρο DOM στο πεδίο .toBeDetached. Στα εργαλεία chrome dev, εάν έχετε πρόσβαση στην καρτέλα 'προφίλ' και στη συνέχεια 'τραβήξτε στιγμιότυπο σωρού', θα δείτε στην έξοδο:
Μπορείτε να ζήσετε με λίγα, αλλά είναι κακό αν έχετε έναν τόνο. Ειδικά αν για κάποιο λόγο, όπως στο παράδειγμα, το αποθηκεύετε στο πεδίο εφαρμογής. Ολόκληρο το DOM θα αξιολογείται σε κάθε σύνοψη. Το προβληματικό αποσπασμένο δέντρο DOM είναι αυτό με 4 κόμβους. Πώς μπορεί λοιπόν να λυθεί αυτό;
συμπεριλαμβανομένων των αρχείων κεφαλίδας c++
attrs.watchAttribute
Το αποσπασμένο δέντρο DOM με 4 καταχωρήσεις αφαιρείται!
Σε αυτό το παράδειγμα, η οδηγία χρησιμοποιεί το ίδιο πεδίο και αποθηκεύει το στοιχείο DOM στο πεδίο εφαρμογής. Ήταν πιο εύκολο για μένα να το δείξω με αυτόν τον τρόπο. Δεν γίνεται πάντα τόσο άσχημο, καθώς θα μπορούσατε να το αποθηκεύσετε σε μια μεταβλητή. Ωστόσο, θα εξακολουθούσε να καταλαβαίνει τη μνήμη εάν υπήρχε κάποιο κλείσιμο που είχε αναφέρει αυτήν τη μεταβλητή ή οποιοδήποτε άλλο από το ίδιο πεδίο λειτουργίας.
Κάθε φορά που χρειάζεστε μια οδηγία που γνωρίζετε ότι θα χρησιμοποιηθεί σε ένα μόνο μέρος, ή την οποία δεν περιμένετε να έρθετε σε διένεξη με οποιοδήποτε περιβάλλον χρησιμοποιείται, δεν χρειάζεται να χρησιμοποιείτε απομονωμένο πεδίο εφαρμογής. Τον τελευταίο καιρό, υπάρχει μια τάση δημιουργίας επαναχρησιμοποιήσιμων στοιχείων, αλλά γνωρίζετε ότι οι βασικές γωνιακές οδηγίες δεν χρησιμοποιούν καθόλου απομονωμένο πεδίο εφαρμογής;
Υπάρχουν δύο κύριοι λόγοι: δεν μπορείτε να εφαρμόσετε δύο μεμονωμένες οδηγίες πεδίου σε ένα στοιχείο και ενδέχεται να αντιμετωπίσετε προβλήματα με την επεξεργασία ένθεσης / κληρονομιάς / συμβάντων. Ειδικά όσον αφορά την ενσωμάτωση - τα αποτελέσματα μπορεί να μην είναι αυτά που περιμένετε.
Αυτό θα αποτύχει:
scope.$watch()
Και ακόμη και αν χρησιμοποιείτε μόνο μία οδηγία, θα παρατηρήσετε ότι ούτε τα μεμονωμένα μοντέλα πεδίου ούτε τα γεγονότα που μεταδίδονται στο απομονωμένο ScopeDirective δεν θα είναι διαθέσιμα στο AnotherController. Όντας λυπηρό, μπορείτε να κάμψετε και να χρησιμοποιήσετε τη μαγεία ενσωμάτωσης για να το κάνετε - αλλά για τις περισσότερες περιπτώσεις χρήσης, δεν χρειάζεται να απομονωθείτε.
MC.foo
Λοιπόν, δύο ερωτήσεις τώρα:
Υπάρχουν δύο τρόποι, και στους δύο μεταβιβάζετε τιμές σε χαρακτηριστικά. Εξετάστε αυτόν τον MainController:
$watch()
Αυτό ελέγχει αυτήν την προβολή:
MC.foo
Σημειώστε ότι το 'χαρακτηριστικό παρακολούθησης' δεν παρεμβάλλεται. Λειτουργεί όλα, λόγω της μαγείας JS. Εδώ είναι ο ορισμός της οδηγίας:
$parse
Παρατηρήστε ότι $eval
μεταβιβάζεται σε $destroy
χωρίς τα εισαγωγικά! Αυτό σημαίνει ότι αυτό που πραγματικά πέρασε στο $ ρολόι ήταν η συμβολοσειρά function cleanMeUp($interval, $rootScope, $timeout) { var postLink = function (scope, element, attrs) { var rootModelListener = $rootScope.$watch('someModel', function () { // do something }); var myInterval = $interval(function () { // do something in intervals }, 2584); var myTimeout = $timeout(function () { // defer some action here }, 1597); scope.domElement = element; $timeout(function () { // calling $destroy manually for testing purposes scope.$destroy(); }, 987); // here is where the cleanup happens scope.$on('$destroy', function () { // disable the listener rootModelListener(); // cancel the interval and timeout $interval.cancel(myInterval); $timeout.cancel(myTimeout); // nullify the DOM-bound model scope.domElement = null; }); element.on('$destroy', function () { // this is a jQuery event // clean up all vanilla JavaScript / jQuery artifacts here // respectful jQuery plugins have $destroy handlers, // that is the reason why this event is emitted... // follow the standards. }); };
! Λειτουργεί, ωστόσο, επειδή οποιαδήποτε συμβολοσειρά μεταβιβάστηκε σε $destroy
αξιολογείται με βάση το εύρος και $digest()
είναι διαθέσιμο στο πεδίο εφαρμογής. Αυτός είναι επίσης ο πιο συνηθισμένος τρόπος που τα χαρακτηριστικά παρακολουθούνται στις βασικές οδηγίες του AngularJS.
Δείτε τον κωδικό στο github για το πρότυπο και εξετάστε το {{ model }}
και
για ακόμη μεγαλύτερη αίσθηση.
Το AngularJS δουλεύει για λογαριασμό σας, αλλά όχι για όλους. Τα ακόλουθα πρέπει να καθαριστούν χειροκίνητα:
vastArray
ΕκδήλωσηΕάν δεν το κάνετε χειροκίνητα, θα αντιμετωπίσετε απροσδόκητες συμπεριφορές και διαρροές μνήμης. Ακόμα χειρότερα - αυτά δεν θα είναι άμεσα ορατά, αλλά τελικά θα υψωθούν. Ο νόμος του Μέρφι.
Εκπληκτικά, το AngularJS παρέχει εύχρηστους τρόπους αντιμετώπισης όλων αυτών:
item.velocity
Παρατηρήστε το jQuery {{ someModel }}
Εκδήλωση. Ονομάζεται όπως το AngularJS, αλλά αντιμετωπίζεται ξεχωριστά. Οι θεατές $ Scope δεν θα αντιδράσουν στο συμβάν jQuery.
Αυτό πρέπει να είναι πολύ απλό τώρα. Υπάρχει ένα πράγμα που πρέπει να καταλάβετε εδώ: ng-if
. Για κάθε δέσμευση ng-repeat
, το AngularJS δημιουργεί ένα πρόγραμμα παρακολούθησης. Σε κάθε φάση πέψης, κάθε τέτοια δέσμευση αξιολογείται και συγκρίνεται με την προηγούμενη τιμή. Αυτό ονομάζεται βρώμικος έλεγχος και αυτό κάνει το $ digest. Εάν η τιμή άλλαξε από τον τελευταίο έλεγχο, ενεργοποιείται η επιστροφή κλήσης του παρατηρητή. Εάν αυτή η επιστροφή κλήσης παρακολούθησης τροποποιήσει ένα μοντέλο (μεταβλητή εύρους $), ενεργοποιείται ένας νέος κύκλος εύρεσης $ (έως και 10 το πολύ) όταν πραγματοποιείται εξαίρεση.
Τα προγράμματα περιήγησης δεν έχουν προβλήματα ακόμη και με χιλιάδες δεσμεύσεις, εκτός εάν οι εκφράσεις είναι περίπλοκες. Η κοινή απάντηση για το «πόσα άτομα παρακολουθούν είναι εντάξει» είναι το 2000.
Λοιπόν, πώς μπορούμε να περιορίσουμε τον αριθμό των παρατηρητών; Μη παρακολουθώντας μοντέλα εμβέλειας όταν δεν περιμένουμε να αλλάξουν. Είναι αρκετά εύκολο και μετά από το AngularJS 1.3, δεδομένου ότι οι εφάπαξ συνδέσεις είναι πλέον πυρήνες.
$watch()
Μετά $Watchers
και $watch()
αξιολογούνται μία φορά, δεν θα αλλάξουν ποτέ ξανά. Μπορείτε ακόμα να εφαρμόσετε φίλτρα στον πίνακα, θα λειτουργούν μια χαρά. Είναι απλώς ότι ο ίδιος ο πίνακας δεν θα αξιολογηθεί. Σε πολλές περιπτώσεις, αυτή είναι μια νίκη.
Αυτό το σφάλμα AngularJS είχε ήδη καλυφθεί εν μέρει στα λάθη 9.β και στο 13. Αυτή είναι μια πιο λεπτομερής εξήγηση. Το AngularJS ενημερώνει το DOM ως αποτέλεσμα των λειτουργιών επανάκλησης στους παρατηρητές. Κάθε δεσμευτική, αυτή είναι η οδηγία $rootScope.$digest()
δημιουργεί παρατηρητές, αλλά οι θεατές έχουν επίσης ρυθμιστεί για πολλές άλλες οδηγίες όπως $scope
και $watch()
. Απλώς ρίξτε μια ματιά στον πηγαίο κώδικα, είναι πολύ ευανάγνωστο. Οι θεατές μπορούν επίσης να ρυθμιστούν χειροκίνητα και πιθανώς το έχετε κάνει τουλάχιστον μερικές φορές εσείς.
$digest()
ers δεσμεύονται σε πεδία. .$apply
μπορεί να λάβει συμβολοσειρές, οι οποίες αξιολογούνται με βάση το εύρος που το .$applyAsync
δεσμεύτηκε να. Μπορούν επίσης να αξιολογήσουν τις λειτουργίες. Και λαμβάνουν επίσης επιστροφές. Λοιπόν, όταν .$evalAsync
καλείται, όλα τα καταχωρημένα μοντέλα (δηλαδή $digest()
μεταβλητές) αξιολογούνται και συγκρίνονται με τις προηγούμενες τιμές τους. Εάν οι τιμές δεν ταιριάζουν, η επιστροφή στο $digest()
εκτελείται.
Είναι σημαντικό να καταλάβετε ότι, παρόλο που η τιμή ενός μοντέλου άλλαξε, η επιστροφή κλήσης δεν ενεργοποιείται μέχρι την επόμενη φάση πέψης. Ονομάζεται «φάση» για έναν λόγο - μπορεί να αποτελείται από διάφορους κύκλους χώνευσης. Εάν μόνο ένας παρατηρητής αλλάξει ένα μοντέλο εμβέλειας, εκτελείται ένας άλλος κύκλος χώνευσης.
Αλλά describe('some module', function () { it('should call the name-it service…', function () { // leave this empty for now }); ... });
δεν έχει δημοσκοπηθεί . Καλείται από βασικές οδηγίες, υπηρεσίες, μεθόδους κ.λπ. Εάν αλλάξετε ένα μοντέλο από μια προσαρμοσμένη συνάρτηση που δεν καλεί 'use strict'; var gulp = require('gulp'); var args = require('yargs').argv; var browserSync = require('browser-sync'); var karma = require('gulp-karma'); var protractor = require('gulp-protractor').protractor; var webdriverUpdate = require('gulp-protractor').webdriver_update; function test() { // Be sure to return the stream // NOTE: Using the fake './foobar' so as to run the files // listed in karma.conf.js INSTEAD of what was passed to // gulp.src ! return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'run' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); this.emit('end'); //instead of erroring the stream, end it }); } function tdd() { return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'start' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); // this.emit('end'); // not ending the stream here }); } function runProtractor () { var argument = args.suite || 'all'; // NOTE: Using the fake './foobar' so as to run the files // listed in protractor.conf.js, instead of what was passed to // gulp.src return gulp.src('./foobar') .pipe(protractor({ configFile: 'test/protractor.conf.js', args: ['--suite', argument] })) .on('error', function (err) { // Make sure failed tests cause gulp to exit non-zero throw err; }) .on('end', function () { // Close browser sync server browserSync.exit(); }); } gulp.task('tdd', tdd); gulp.task('test', test); gulp.task('test-e2e', ['webdriver-update'], runProtractor); gulp.task('webdriver-update', webdriverUpdate);
, console.log()
, debugInfo
ή οτιδήποτε άλλο που καλεί τελικά $(document.body).scope().$root
, οι δεσμεύσεις δεν θα ενημερωθούν.
Παρεμπιπτόντως, ο πηγαίος κώδικας για $(
Κορυφαία 18 πιο συνηθισμένα λάθη που κάνουν οι προγραμματιστές AngularJS
Οι εφαρμογές μιας σελίδας απαιτούν από τους προγραμματιστές front-end να γίνουν καλύτεροι μηχανικοί λογισμικού. Το CSS και το HTML δεν αποτελούν πλέον τη μεγαλύτερη ανησυχία, στην πραγματικότητα, δεν υπάρχει πλέον μόνο μία ανησυχία. Ο προγραμματιστής front-end πρέπει να χειρίζεται XHRs, λογική εφαρμογών (μοντέλα, προβολές, ελεγκτές), απόδοση, κινούμενα σχέδια, στυλ, δομή, SEO και ενσωμάτωση με εξωτερικές υπηρεσίες. Το αποτέλεσμα που προκύπτει από όλους αυτούς τους συνδυασμούς είναι η Εμπειρία χρήστη (UX) που πρέπει πάντα να έχει προτεραιότητα.
Το AngularJS είναι ένα πολύ ισχυρό πλαίσιο. Είναι το τρίτο αποθετήριο με τα περισσότερα αστέρια στο GitHub. Δεν είναι δύσκολο να αρχίσετε να χρησιμοποιείτε, αλλά οι στόχοι που προορίζεται να επιτύχει την κατανόηση της ζήτησης. Δεν μπορούν πλέον οι προγραμματιστές της AngularJS να αγνοήσουν την κατανάλωση μνήμης, επειδή δεν θα επαναφερθούν πλέον στην πλοήγηση. Αυτή είναι η εμπροσθοφυλακή του Ανάπτυξη διαδικτύου . Ας το αγκαλιάσουμε!
Συνιστώνται μερικές τροποποιήσεις βελτιστοποίησης για παραγωγή. Ένα από αυτά είναι η απενεργοποίηση των πληροφοριών εντοπισμού σφαλμάτων.
DebugInfoEnabled
είναι μια ρύθμιση που από προεπιλογή είναι αληθής και επιτρέπει την πρόσβαση στο εύρος μέσω κόμβων DOM. Εάν θέλετε να το δοκιμάσετε μέσω της κονσόλας JavaScript, επιλέξτε ένα στοιχείο DOM και αποκτήστε πρόσβαση στο πεδίο εφαρμογής του με:
angular.element(document.body).scope()
Μπορεί να είναι χρήσιμο ακόμη και όταν δεν το χρησιμοποιείτε jQuery με το CSS του, αλλά δεν πρέπει να χρησιμοποιείται εκτός της κονσόλας. Ο λόγος είναι ότι όταν $compileProvider.debugInfoEnabled
έχει οριστεί σε false, κλήση .scope()
σε έναν κόμβο DOM θα επιστρέψει undefined
.
Αυτή είναι μία από τις λίγες προτεινόμενες επιλογές παραγωγής.
Λάβετε υπόψη ότι εξακολουθείτε να έχετε πρόσβαση στο πεδίο εφαρμογής μέσω της κονσόλας, ακόμα και όταν είστε σε παραγωγή. Κλήση angular.reloadWithDebugInfo()
από την κονσόλα και η εφαρμογή θα κάνει ακριβώς αυτό.
Ίσως το έχετε διαβάσει αν ήσασταν δεν έχει τελεία στο μοντέλο ng , το κάνατε λάθος. Όσον αφορά την κληρονομιά, αυτή η δήλωση είναι συχνά αληθινή. Τα πεδία έχουν ένα πρωτότυπο μοντέλο κληρονομιάς, τυπικό στη JavaScript και τα ένθετα πεδία είναι κοινά για το AngularJS. Πολλές οδηγίες δημιουργούν παιδικά πεδία όπως ngRepeat
, ngIf
, και ngController
. Κατά την επίλυση ενός μοντέλου, η αναζήτηση ξεκινά από το τρέχον εύρος και περνά από κάθε γονικό εύρος, μέχρι το $rootScope
.
Όμως, όταν ορίζετε μια νέα τιμή, αυτό που συμβαίνει εξαρτάται από το είδος του μοντέλου (μεταβλητή) που θέλουμε να αλλάξουμε. Εάν το μοντέλο είναι πρωτόγονο, το πεδίο εφαρμογής για παιδιά θα δημιουργήσει ένα νέο μοντέλο. Αλλά αν η αλλαγή αφορά μια ιδιότητα ενός αντικειμένου μοντέλου, η αναζήτηση στα γονικά πεδία θα βρει το αντικείμενο αναφοράς και θα αλλάξει την πραγματική του ιδιότητα. Ένα νέο μοντέλο δεν θα οριστεί στο τρέχον εύρος, οπότε δεν θα γίνει κάλυψη:
function MainController($scope) { $scope.foo = 1; $scope.bar = {innerProperty: 2}; } angular.module('myApp', []) .controller('MainController', MainController);
OUTER SCOPE:
{{ foo }}
{{ bar.innerProperty }}
INNER SCOPE
{{ foo }}
{{ bar.innerProperty }}
Set primitive Mutate object
Κάνοντας κλικ στο κουμπί με την ένδειξη 'Set primitive' θα ορίσετε το foo στο εσωτερικό πεδίο σε 2, αλλά δεν θα αλλάξει foo στο εξωτερικό πεδίο.
Κάνοντας κλικ στο κουμπί 'Αλλαγή αντικειμένου' θα αλλάξει η ιδιότητα της γραμμής από το γονικό πεδίο. Δεδομένου ότι δεν υπάρχει μεταβλητή στο εσωτερικό πεδίο, δεν θα υπάρξει σκίαση και η ορατή τιμή για τη γραμμή θα είναι 3 και στα δύο πεδία.
Ένας άλλος τρόπος για να το κάνετε αυτό είναι να αξιοποιήσετε το γεγονός ότι τα γονικά πεδία και το ριζικό πεδίο αναφοράς αναφέρονται από κάθε πεδίο. Το $parent
και $root
αντικείμενα μπορούν να χρησιμοποιηθούν για πρόσβαση στο γονικό εύρος και $rootScope
, αντίστοιχα, απευθείας από την προβολή. Μπορεί να είναι ένας ισχυρός τρόπος, αλλά δεν είμαι οπαδός του λόγω του προβλήματος με τη στόχευση ενός συγκεκριμένου πεδίου στο ρεύμα. Υπάρχει ένας άλλος τρόπος για να ορίσετε και να αποκτήσετε πρόσβαση σε συγκεκριμένες ιδιότητες για ένα εύρος - χρησιμοποιώντας το controllerAs
σύνταξη.
Ο εναλλακτικός και αποτελεσματικότερος τρόπος για να αντιστοιχίσετε μοντέλα για χρήση αντικειμένου ελεγκτή αντί για το πεδίο εμβολιασμού $. Αντί να εισάγουμε πεδίο εφαρμογής, μπορούμε να ορίσουμε μοντέλα ως εξής:
function MainController($scope) { this.foo = 1; var that = this; var setBar = function () { // that.bar = {someProperty: 2}; this.bar = {someProperty: 2}; }; setBar.call(this); // there are other conventions: // var MC = this; // setBar.call(this); when using 'this' inside setBar() }
OUTER SCOPE:
{{ MC.foo }}
{{ MC.bar.someProperty }}
INNER SCOPE
{{ MC.foo }}
{{ MC.bar.someProperty }}
Change MC.foo Change MC.bar.someProperty
Αυτό είναι πολύ λιγότερο συγκεχυμένο. Ειδικά όταν υπάρχουν πολλά ένθετα πεδία, όπως μπορεί να συμβαίνει με τις ένθετες καταστάσεις.
Υπάρχουν περισσότερα για τη σύνταξη του ελεγκτή.
Υπάρχουν μερικές προειδοποιήσεις σχετικά με το πώς εκτίθεται το αντικείμενο του ελεγκτή. Είναι βασικά ένα αντικείμενο που τίθεται στο πεδίο εφαρμογής του ελεγκτή, ακριβώς όπως ένα κανονικό μοντέλο.
Εάν πρέπει να παρακολουθήσετε μια ιδιότητα του αντικειμένου ελεγκτή, μπορείτε να παρακολουθήσετε μια συνάρτηση, αλλά δεν απαιτείται. Εδώ είναι ένα παράδειγμα:
function MainController($scope) { this.title = 'Some title'; $scope.$watch(angular.bind(this, function () { return this.title; }), function (newVal, oldVal) { // handle changes }); }
Είναι πιο εύκολο να κάνετε:
function MainController($scope) { this.title = 'Some title'; $scope.$watch('MC.title', function (newVal, oldVal) { // handle changes }); }
Αυτό σημαίνει επίσης ότι κάτω από την αλυσίδα πεδίου, θα μπορούσατε να έχετε πρόσβαση στο MC από έναν θυγατρικό ελεγκτή:
function NestedController($scope) { if ($scope.MC && $scope.MC.title === 'Some title') { $scope.MC.title = 'New title'; } }
Ωστόσο, για να μπορέσετε να το κάνετε αυτό πρέπει να είστε συνεπείς με το ακρωνύμιο που χρησιμοποιείτε για τον ελεγκτήA. Υπάρχουν τουλάχιστον τρεις τρόποι για να το ρυθμίσετε. Είδατε ήδη το πρώτο:
…
Ωστόσο, εάν χρησιμοποιείτε ui-router
, ορίζοντας έναν ελεγκτή με αυτόν τον τρόπο είναι επιρρεπές σε λάθη. Για καταστάσεις, οι ελεγκτές πρέπει να καθορίζονται στη διαμόρφωση κατάστασης:
angular.module('myApp', []) .config(function ($stateProvider) { $stateProvider .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/path/to/template.html' }) }). controller('MainController', function () { … });
Υπάρχει ένας άλλος τρόπος σχολιασμού:
(…) .state('main', { url: '/', controller: 'MainController', controllerAs: 'MC', templateUrl: '/path/to/template.html' })
Μπορείτε να κάνετε το ίδιο στις οδηγίες:
function AnotherController() { this.text = 'abc'; } function testForApeeScape() { return { controller: 'AnotherController as AC', template: '{{ AC.text }}
' }; } angular.module('myApp', []) .controller('AnotherController', AnotherController) .directive('testForApeeScape', testForApeeScape);
Ο άλλος τρόπος σχολιασμού ισχύει επίσης, αν και λιγότερο συνοπτικός:
function testForApeeScape() { return { controller: 'AnotherController', controllerAs: 'AC', template: '{{ AC.text }}
' }; }
Η de facto λύση δρομολόγησης για το AngularJS ήταν, μέχρι τώρα, η ui-router
. Καταργήθηκε από τον πυρήνα πριν από λίγο, το ngRoute module, ήταν πολύ βασικό για πιο εξελιγμένη δρομολόγηση.
Υπάρχει ένα νέο NgRouter
στο δρόμο, αλλά οι συγγραφείς το θεωρούν πολύ νωρίς για παραγωγή. Όταν το γράφω αυτό, η σταθερή γωνιακή τιμή είναι 1.3.15 και ui-router
πετρώματα.
Οι κύριοι λόγοι:
Εδώ θα καλύψω κατάσταση ένθεσης για την αποφυγή σφαλμάτων AngularJS.
Σκεφτείτε το ως μια περίπλοκη αλλά τυπική περίπτωση χρήσης. Υπάρχει μια εφαρμογή, η οποία έχει προβολή αρχικής σελίδας και προβολή προϊόντος. Η προβολή προϊόντος έχει τρεις ξεχωριστές ενότητες: την εισαγωγή, το widget και το περιεχόμενο. Θέλουμε το widget να παραμένει και να μην φορτώνεται ξανά κατά την εναλλαγή μεταξύ της κατάστασης. Αλλά το περιεχόμενο πρέπει να φορτωθεί ξανά.
Εξετάστε την ακόλουθη δομή σελίδας ευρετηρίου προϊόντων HTML:
SOME PRODUCT SPECIFIC INTRO
Product title
Context-specific content
Αυτό είναι κάτι που θα μπορούσαμε να πάρουμε από τον κωδικοποιητή HTML και τώρα πρέπει να τον χωρίσουμε σε αρχεία και καταστάσεις. Γενικά συμφωνώ με τη σύμβαση ότι υπάρχει μια αφηρημένη ΚΥΡΙΑ κατάσταση, η οποία διατηρεί τα παγκόσμια δεδομένα, εάν χρειάζεται. Χρησιμοποιήστε αυτό αντί για $ rootScope. ο Κύριος Η κατάσταση θα διατηρήσει επίσης στατικό HTML που απαιτείται σε κάθε σελίδα. Κρατώ το index.html καθαρό.
function config($stateProvider) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { abstract: true, url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html' }) // A SIMPLE HOMEPAGE .state('main.homepage', { url: '', controller: 'HomepageController as HC', templateUrl: '/routing-demo/homepage.html' }) // THE ABOVE IS ALL GOOD, HERE IS TROUBLE // A COMPLEX PRODUCT PAGE .state('main.product', { abstract: true, url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'widget': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }); } angular.module('articleApp', [ 'ui.router' ]) .config(config);
main.product.index
Στη συνέχεια, ας δούμε τη σελίδα ευρετηρίου προϊόντων:
main.product.details
Όπως μπορείτε να δείτε, η σελίδα ευρετηρίου προϊόντων έχει τρεις προβολές με όνομα. Ένα για την εισαγωγή, ένα για το widget και ένα για το προϊόν. Γνωρίζουμε τις προδιαγραφές! Ας ρυθμίσουμε λοιπόν τη δρομολόγηση:
ui-router
Αυτή θα ήταν η πρώτη προσέγγιση. Τώρα, τι συμβαίνει κατά την εναλλαγή μεταξύ // A COMPLEX PRODUCT PAGE // WITH NO MORE TROUBLE .state('main.product', { abstract: true, url: ':id', views: { // TARGETING THE UNNAMED VIEW IN MAIN.HTML '@main': { controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html' }, // TARGETING THE WIDGET VIEW IN PRODUCT.HTML // BY DEFINING A CHILD VIEW ALREADY HERE, WE ENSURE IT DOES NOT RELOAD ON CHILD STATE CHANGE ' [email protected] ': { controller: 'WidgetController as PWC', templateUrl: '/routing-demo/widget.html' } } }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } }) // PRODUCT DETAILS SUBSTATE .state('main.product.details', { url: '/details', views: { 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } } });
και $urlRouterProvider.deferIntercept()
; Το περιεχόμενο και το widget φορτώνονται ξανά, αλλά θέλουμε μόνο να φορτώσουμε ξανά το περιεχόμενο. Αυτό ήταν προβληματικό και οι προγραμματιστές δημιούργησαν πραγματικά δρομολογητές που θα υποστήριζαν μόνο αυτήν τη λειτουργία. Ένα από τα ονόματα για αυτό ήταν κολλώδη θέα . Ευτυχώς, 'use strict'; function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // THE BELOW CALL CANNOT BE SPIED ON WITH JASMINE publicMethod1('someArgument'); }; // IF THE LITERAL IS RETURNED THIS WAY, IT CAN'T BE REFERRED TO FROM INSIDE return { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; } angular.module('app', []) .factory('yoda', yoda);
υποστηρίζει αυτό έξω από το κουτί με απόλυτη στόχευση προβολής .
publicMethod1
Μετακινώντας τον ορισμό της κατάστασης στην προβολή γονέα, η οποία είναι επίσης αφηρημένη, μπορούμε να διατηρήσουμε την προβολή του παιδιού από την επαναφόρτωση κατά την εναλλαγή διευθύνσεων URL που επηρεάζουν κανονικά τα αδέλφια αυτού του παιδιού. Φυσικά, το widget θα μπορούσε να είναι μια απλή οδηγία. Αλλά το θέμα είναι, θα μπορούσε επίσης να είναι μια άλλη πολύπλοκη ένθετη κατάσταση.
Υπάρχει ένας άλλος τρόπος για να το κάνετε αυτό μέσω της χρήσης function yoda() { var privateMethod = function () { // this function is not exposed }; var publicMethod1 = function () { // this function is exposed, but it's internals are not exposed // some logic... }; var publicMethod2 = function (arg) { // the below call cannot be spied on publicMethod1('someArgument'); // BUT THIS ONE CAN! hostObject.publicMethod1('aBetterArgument'); }; var hostObject = { publicMethod1: function () { return publicMethod1(); }, publicMethod2: function (arg) { return publicMethod2(arg); } }; return hostObject; }
, αλλά πιστεύω ότι η χρήση της διαμόρφωσης κατάστασης είναι στην πραγματικότητα καλύτερη. Αν ενδιαφέρεστε να παρακολουθήσετε διαδρομές, έγραψα ένα μικρό σεμινάριο Υπερχείλιση στοίβας .
Αυτό λάθος είναι ελαφρύτερου διαμετρήματος και είναι περισσότερο θέμα στυλ από την αποφυγή μηνυμάτων σφάλματος AngularJS. Ίσως έχετε παρατηρήσει στο παρελθόν ότι σπάνια μεταφέρω ανώνυμες συναρτήσεις στις γωνιακές εσωτερικές δηλώσεις. Συνήθως απλώς ορίζω μια συνάρτηση πρώτα και μετά την περνάω.
Αυτό αφορά κάτι περισσότερο από απλές λειτουργίες. Πήρα αυτήν την προσέγγιση από οδηγούς στυλ ανάγνωσης, ειδικά από τους Airbnb και Todd Motto. Πιστεύω ότι υπάρχουν πολλά πλεονεκτήματα και σχεδόν κανένα μειονέκτημα σε αυτό.
Πρώτα απ 'όλα, μπορείτε να χειριστείτε και να μεταλλάξετε τις λειτουργίες και τα αντικείμενά σας πολύ πιο εύκολα εάν έχουν αντιστοιχιστεί σε μια μεταβλητή. Δεύτερον, ο κώδικας είναι καθαρότερος και μπορεί εύκολα να χωριστεί σε αρχεία. Αυτό σημαίνει συντηρησιμότητα. Εάν δεν θέλετε να μολύνετε τον παγκόσμιο χώρο ονομάτων, τυλίξτε κάθε αρχείο σε IIFE. Ο τρίτος λόγος είναι η δυνατότητα δοκιμής. Εξετάστε αυτό το παράδειγμα:
function scoringService($q) { var scoreItems = function (items, weights) { var deferred = $q.defer(); var worker = new Worker('/worker-demo/scoring.worker.js'); var orders = { items: items, weights: weights }; worker.postMessage(orders); worker.onmessage = function (e) { if (e.data && e.data.ready) { deferred.resolve(e.data.items); } }; return deferred.promise; }; var hostObject = { scoreItems: function (items, weights) { return scoreItems(items, weights); } }; return hostObject; } angular.module('app.worker') .factory('scoringService', scoringService);
Τώρα λοιπόν θα μπορούσαμε να χλευάσουμε το 'use strict'; function scoringFunction(items, weights) { var itemsArray = []; for (var i = 0; i b.sum) { return -1; } else if (a.sum
scoringService.scoreItems()
Αυτό δεν αφορά μόνο το στυλ, καθώς στην πραγματικότητα ο κώδικας είναι πιο επαναχρησιμοποιήσιμος και ιδιωματικός. Ο προγραμματιστής αποκτά περισσότερη εκφραστική ισχύ. Ο διαχωρισμός όλων των κωδικών σε αυτόνομα μπλοκ απλώς διευκολύνει.
Σε ορισμένα σενάρια, μπορεί να απαιτείται η επεξεργασία μιας μεγάλης σειράς σύνθετων αντικειμένων περνώντας τα μέσα από ένα σύνολο φίλτρων, διακοσμητών και τέλος ενός αλγορίθμου ταξινόμησης. Μία περίπτωση χρήσης είναι όταν η εφαρμογή πρέπει να λειτουργεί εκτός σύνδεσης ή όπου η απόδοση της εμφάνισης δεδομένων είναι βασική. Και δεδομένου ότι το JavaScript είναι μονόκλωνο, είναι σχετικά εύκολο να παγώσει το πρόγραμμα περιήγησης.
Είναι επίσης εύκολο να το αποφύγετε με τους εργαζόμενους στο Διαδίκτυο. Δεν φαίνεται να υπάρχουν δημοφιλείς βιβλιοθήκες που να το χειρίζονται ειδικά για το AngularJS. Μπορεί όμως να είναι το καλύτερο, καθώς η εφαρμογή είναι εύκολη.
Αρχικά, ας ρυθμίσουμε την υπηρεσία:
.toString()
Τώρα, ο εργαζόμενος:
function resolve(index, timeout) { return { data: function($q, $timeout) { var deferred = $q.defer(); $timeout(function () { deferred.resolve(console.log('Data resolve called ' + index)); }, timeout); return deferred.promise; } }; } function configResolves($stateProvide) { $stateProvider // MAIN ABSTRACT STATE, ALWAYS ON .state('main', { url: '/', controller: 'MainController as MC', templateUrl: '/routing-demo/main.html', resolve: resolve(1, 1597) }) // A COMPLEX PRODUCT PAGE .state('main.product', { url: ':id', controller: 'ProductController as PC', templateUrl: '/routing-demo/product.html', resolve: resolve(2, 2584) }) // PRODUCT DEFAULT SUBSTATE .state('main.product.index', { url: '', views: { 'intro': { controller: 'IntroController as PIC', templateUrl: '/routing-demo/intro.html' }, 'content': { controller: 'ContentController as PCC', templateUrl: '/routing-demo/content.html' } }, resolve: resolve(3, 987) }); }
Τώρα, εγχύστε την υπηρεσία ως συνήθως και αντιμετωπίστε Data resolve called 3 Data resolve called 1 Data resolve called 2 Main Controller executed Product Controller executed Intro Controller executed
όπως θα κάνατε οποιαδήποτε μέθοδο υπηρεσίας που επιστρέφει μια υπόσχεση. Η βαριά επεξεργασία θα πραγματοποιηθεί σε ξεχωριστό νήμα και δεν θα προκληθεί ζημιά στο UX.
Τι να προσέξετε:
.run()
στην ιδιότητα που πέρασε και λειτούργησε σωστά.Το Resolves προσθέτει επιπλέον χρόνο στη φόρτωση της προβολής. Πιστεύω ότι η υψηλή απόδοση της εφαρμογής front-end είναι ο πρωταρχικός μας στόχος. Δεν πρέπει να αποτελεί πρόβλημα η απόδοση ορισμένων τμημάτων της προβολής ενώ η εφαρμογή περιμένει τα δεδομένα από το API.
Εξετάστε αυτήν τη ρύθμιση:
this.maxPrice = '100'; this.price = '55'; $scope.$watch('MC.price', function (newVal) { if (newVal || newVal === 0) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } });
Η έξοδος της κονσόλας θα είναι:
this.maxPrice = '100'; this.price = '55'; this.priceTemporary = '55'; $scope.$watch('MC.price', function (newVal) { if (!isNaN(newVal)) { for (var i = 0; i <987; i++) { console.log('ALL YOUR BASE ARE BELONG TO US'); } } }); var timeoutInstance; $scope.$watch('MC.priceTemporary', function (newVal) { if (!isNaN(newVal)) { if (timeoutInstance) { $timeout.cancel(timeoutInstance); } timeoutInstance = $timeout(function () { $scope.MC.price = newVal; }, 144); } });
Αυτό βασικά σημαίνει ότι:
Αυτό σημαίνει ότι πριν ο χρήστης δει οποιαδήποτε έξοδο, πρέπει να περιμένει όλες τις εξαρτήσεις. Πρέπει να έχουμε αυτά τα δεδομένα, σίγουρα, εντάξει. Εάν είναι απολύτως απαραίτητο να το έχετε πριν από την προβολή, τοποθετήστε το σε ένα $digest()
ΟΙΚΟΔΟΜΙΚΟ ΤΕΤΡΑΓΩΝΟ. Διαφορετικά, απλώς πραγματοποιήστε την κλήση προς την υπηρεσία από τον ελεγκτή και χειριστείτε την κατάσταση μισής φόρτωσης. Βλέποντας την εργασία σε εξέλιξη - και ο ελεγκτής έχει ήδη εκτελεστεί, οπότε στην πραγματικότητα είναι πρόοδος - είναι καλύτερο από το να σταματήσει η εφαρμογή.
α) Προκαλεί πάρα πολλούς βρόχους πέψης, όπως η τοποθέτηση ρυθμιστικών σε μοντέλα
Αυτό είναι ένα γενικό πρόβλημα που μπορεί να οδηγήσει σε σφάλματα AngularJS, αλλά θα το συζητήσω στο παράδειγμα των ρυθμιστικών. Χρησιμοποιούσα αυτήν τη ρυθμιστική βιβλιοθήκη, το ρυθμιστικό γωνιακής εμβέλειας, επειδή χρειαζόμουν την εκτεταμένη λειτουργικότητα. Αυτή η οδηγία έχει αυτήν τη σύνταξη στην ελάχιστη έκδοση:
ng-click
Εξετάστε τον ακόλουθο κώδικα στον ελεγκτή:
input
Λοιπόν αυτό λειτουργεί αργά. Η απλή λύση θα ήταν να ορίσετε ένα χρονικό όριο στην είσοδο. Αλλά αυτό δεν είναι πάντα βολικό και μερικές φορές δεν θέλουμε πραγματικά να καθυστερήσουμε την πραγματική αλλαγή μοντέλου σε όλες τις περιπτώσεις.
Θα προσθέσουμε λοιπόν ένα προσωρινό μοντέλο που θα αλλάξει το λειτουργικό μοντέλο στο χρονικό όριο:
$timeout
και στον ελεγκτή:
$http
β) Μη χρήση του $ applyAsync
Το AngularJS δεν διαθέτει μηχανισμό ψηφοφορίας για κλήση $watch
. Εκτελείται μόνο επειδή χρησιμοποιούμε τις οδηγίες (π.χ. .$applyAsync()
, $digest()
), υπηρεσίες (applyAsync
, $http
) και μεθόδους (mymodule.config(function ($httpProvider) { $httpProvider.useApplyAsync(true); });
) που αξιολογούν τον κωδικό μας και καλέστε ένα χωνευτήρι μετά.
Τι .click()
το κάνει είναι να καθυστερήσει την ανάλυση των εκφράσεων μέχρι την επόμενη $apply()
κύκλος, ο οποίος ενεργοποιείται μετά από ένα χρονικό όριο 0, που στην πραγματικότητα είναι ~ 10ms.
Υπάρχουν δύο τρόποι χρήσης $scope.$root.$digest()
τώρα. Ένας αυτοματοποιημένος τρόπος για $rootScope.$digest()
αιτήματα και έναν χειροκίνητο τρόπο για τα υπόλοιπα.
Για να κάνετε όλα τα αιτήματα http που επιστρέφουν περίπου την ίδια ώρα να επιλυθούν σε μία σύνοψη, κάντε:
$scope.$digest()
Ο χειροκίνητος τρόπος δείχνει πώς λειτουργεί πραγματικά. Εξετάστε κάποια λειτουργία που εκτελείται με την επιστροφή κλήσης σε έναν ακροατή συμβάντων vanilla JS ή ένα jQuery I AM DIRECTIVE$scope.$applyAsync()
ή κάποια άλλη εξωτερική βιβλιοθήκη. Αφού εκτελέσει και αλλάξει μοντέλα, αν δεν το έχετε ήδη τυλίξει σε $digest()
πρέπει να καλέσετε remove directive
(function MainController($rootScope, $scope) { this.removeDirective = function () { $rootScope.$emit('destroyDirective'); }; } function testForApeeScape($rootScope, $timeout) { return { link: function (scope, element, attributes) { var destroyListener = $rootScope.$on('destroyDirective', function () { scope.$destroy(); }); // adding a timeout for the DOM to get ready $timeout(function () { scope.toBeDetached = element.find('p'); }); scope.$on('$destroy', function () { destroyListener(); element.remove(); }); }, template: '
), ή τουλάχιστον scope.$on('$destroy', function () { // setting this model to null // will solve the problem. scope.toBeDetached = null; destroyListener(); element.remove(); });
. Διαφορετικά, δεν θα δείτε καμία αλλαγή.
Εάν το κάνετε πολλές φορές σε μία ροή, ενδέχεται να αρχίσει να λειτουργεί αργά. Εξετάστε το ενδεχόμενο να καλέσετε … the isolated scope is not available here, look: {{ isolatedModel }}
στις εκφράσεις αντ 'αυτού. Θα ορίσει μόνο έναν κύκλο χώνευσης για όλους τους.
γ) Κάνοντας βαριά επεξεργασία εικόνων
Εάν αντιμετωπίζετε κακή απόδοση, μπορείτε να διερευνήσετε τον λόγο χρησιμοποιώντας το Χρονολόγιο από τα Εργαλεία προγραμματιστών Chrome. Θα γράψω περισσότερα για αυτό το εργαλείο κατά λάθος # 17. Εάν το γράφημα χρονοδιάγραμμα κυριαρχείται με το πράσινο χρώμα μετά την εγγραφή, τα ζητήματα απόδοσής σας μπορεί να σχετίζονται με την επεξεργασία εικόνων. Αυτό δεν σχετίζεται αυστηρά με το AngularJS, αλλά μπορεί να συμβεί πάνω από ζητήματα απόδοσης του AngularJS (τα οποία θα ήταν κυρίως κίτρινα στο γράφημα). Ως μηχανικοί front-end, πρέπει να σκεφτούμε το ολοκληρωμένο έργο.
Αφιερώστε λίγο χρόνο για να αξιολογήσετε:
Εάν απαντήσατε «ναι» σε τουλάχιστον τρία από τα παραπάνω, σκεφτείτε να το χαλαρώσετε. Ίσως μπορείτε να προβάλλετε διάφορα μεγέθη εικόνας και να μην αλλάξετε μέγεθος. Ίσως θα μπορούσατε να προσθέσετε το 'transform: translateZ (0)' εξαναγκαστική επεξεργασία επεξεργασίας GPU. Ή χρησιμοποιήστε το requestAnimationFrame για χειριστές.
Πολλές φορές πιθανότατα ακούτε ότι δεν συνιστάται η χρήση του jQuery με το AngularJS και ότι πρέπει να αποφεύγεται. Είναι επιτακτική ανάγκη να κατανοήσουμε τον λόγο πίσω από αυτές τις δηλώσεις. Υπάρχουν τουλάχιστον τρεις λόγοι, κατά τη γνώμη μου, αλλά κανένας από αυτούς δεν είναι πραγματικοί αποκλειστές.
Λόγος 1: Όταν εκτελείτε τον κωδικό jQuery, πρέπει να καλέσετε function MainController($interval) { this.foo = { bar: 1 }; this.baz = 1; var that = this; $interval(function () { that.foo.bar++; }, 144); $interval(function () { that.baz++; }, 144); this.quux = [1,2,3]; }
ο ίδιος. Για πολλές περιπτώσεις, υπάρχει Λύση AngularJS το οποίο είναι προσαρμοσμένο για AngularJS και μπορεί να χρησιμοποιηθεί καλύτερα στο Angular από το jQuery (π.χ. ng-click ή το σύστημα συμβάντων).
Λόγος 2: Η μέθοδος σκέψης για τη δημιουργία της εφαρμογής. Εάν προσθέσατε JavaScript σε ιστότοπους, οι οποίοι φορτώνονται ξανά κατά την πλοήγηση, δεν χρειάζεται να ανησυχείτε για την κατανάλωση μνήμης πάρα πολύ. Με εφαρμογές μιας σελίδας, πρέπει να ανησυχείτε. Εάν δεν κάνετε εκκαθάριση, οι χρήστες που αφιερώνουν περισσότερα από λίγα λεπτά στην εφαρμογή σας ενδέχεται να αντιμετωπίσουν αυξανόμενα προβλήματα απόδοσης.
Λόγος 3: Ο καθαρισμός δεν είναι στην πραγματικότητα το πιο εύκολο πράγμα που πρέπει να κάνετε και να αναλύσετε. Δεν υπάρχει τρόπος να καλέσετε έναν συλλέκτη απορριμάτων από το σενάριο (στο πρόγραμμα περιήγησης). Ενδέχεται να καταλήξετε σε ανεξάρτητα δέντρα DOM. Δημιούργησα ένα παράδειγμα (το jQuery φορτώνεται στο index.html):
function testDirective() { var postLink = function (scope, element, attrs) { scope.$watch(attrs.watchAttribute, function (newVal) { if (newVal) { // take a look in the console // we can't use the attribute directly console.log(attrs.watchAttribute); // the newVal is evaluated, and it can be used scope.modifiedFooBar = newVal.bar * 10; } }, true); attrs.$observe('observeAttribute', function (newVal) { scope.observed = newVal; }); }; return { link: postLink, templateUrl: '/attributes-demo/test-directive.html' }; }
Αυτή είναι μια απλή οδηγία που εξάγει κάποιο κείμενο. Υπάρχει ένα κουμπί κάτω από αυτό, το οποίο θα καταστρέψει την οδηγία χειροκίνητα.
Έτσι, όταν καταργηθεί η οδηγία, παραμένει μια αναφορά στο δέντρο DOM στο πεδίο .toBeDetached. Στα εργαλεία chrome dev, εάν έχετε πρόσβαση στην καρτέλα 'προφίλ' και στη συνέχεια 'τραβήξτε στιγμιότυπο σωρού', θα δείτε στην έξοδο:
Μπορείτε να ζήσετε με λίγα, αλλά είναι κακό αν έχετε έναν τόνο. Ειδικά αν για κάποιο λόγο, όπως στο παράδειγμα, το αποθηκεύετε στο πεδίο εφαρμογής. Ολόκληρο το DOM θα αξιολογείται σε κάθε σύνοψη. Το προβληματικό αποσπασμένο δέντρο DOM είναι αυτό με 4 κόμβους. Πώς μπορεί λοιπόν να λυθεί αυτό;
attrs.watchAttribute
Το αποσπασμένο δέντρο DOM με 4 καταχωρήσεις αφαιρείται!
Σε αυτό το παράδειγμα, η οδηγία χρησιμοποιεί το ίδιο πεδίο και αποθηκεύει το στοιχείο DOM στο πεδίο εφαρμογής. Ήταν πιο εύκολο για μένα να το δείξω με αυτόν τον τρόπο. Δεν γίνεται πάντα τόσο άσχημο, καθώς θα μπορούσατε να το αποθηκεύσετε σε μια μεταβλητή. Ωστόσο, θα εξακολουθούσε να καταλαβαίνει τη μνήμη εάν υπήρχε κάποιο κλείσιμο που είχε αναφέρει αυτήν τη μεταβλητή ή οποιοδήποτε άλλο από το ίδιο πεδίο λειτουργίας.
Κάθε φορά που χρειάζεστε μια οδηγία που γνωρίζετε ότι θα χρησιμοποιηθεί σε ένα μόνο μέρος, ή την οποία δεν περιμένετε να έρθετε σε διένεξη με οποιοδήποτε περιβάλλον χρησιμοποιείται, δεν χρειάζεται να χρησιμοποιείτε απομονωμένο πεδίο εφαρμογής. Τον τελευταίο καιρό, υπάρχει μια τάση δημιουργίας επαναχρησιμοποιήσιμων στοιχείων, αλλά γνωρίζετε ότι οι βασικές γωνιακές οδηγίες δεν χρησιμοποιούν καθόλου απομονωμένο πεδίο εφαρμογής;
Υπάρχουν δύο κύριοι λόγοι: δεν μπορείτε να εφαρμόσετε δύο μεμονωμένες οδηγίες πεδίου σε ένα στοιχείο και ενδέχεται να αντιμετωπίσετε προβλήματα με την επεξεργασία ένθεσης / κληρονομιάς / συμβάντων. Ειδικά όσον αφορά την ενσωμάτωση - τα αποτελέσματα μπορεί να μην είναι αυτά που περιμένετε.
Αυτό θα αποτύχει:
scope.$watch()
Και ακόμη και αν χρησιμοποιείτε μόνο μία οδηγία, θα παρατηρήσετε ότι ούτε τα μεμονωμένα μοντέλα πεδίου ούτε τα γεγονότα που μεταδίδονται στο απομονωμένο ScopeDirective δεν θα είναι διαθέσιμα στο AnotherController. Όντας λυπηρό, μπορείτε να κάμψετε και να χρησιμοποιήσετε τη μαγεία ενσωμάτωσης για να το κάνετε - αλλά για τις περισσότερες περιπτώσεις χρήσης, δεν χρειάζεται να απομονωθείτε.
MC.foo
Λοιπόν, δύο ερωτήσεις τώρα:
Υπάρχουν δύο τρόποι, και στους δύο μεταβιβάζετε τιμές σε χαρακτηριστικά. Εξετάστε αυτόν τον MainController:
$watch()
Αυτό ελέγχει αυτήν την προβολή:
MC.foo
Σημειώστε ότι το 'χαρακτηριστικό παρακολούθησης' δεν παρεμβάλλεται. Λειτουργεί όλα, λόγω της μαγείας JS. Εδώ είναι ο ορισμός της οδηγίας:
$parse
Παρατηρήστε ότι $eval
μεταβιβάζεται σε $destroy
χωρίς τα εισαγωγικά! Αυτό σημαίνει ότι αυτό που πραγματικά πέρασε στο $ ρολόι ήταν η συμβολοσειρά function cleanMeUp($interval, $rootScope, $timeout) { var postLink = function (scope, element, attrs) { var rootModelListener = $rootScope.$watch('someModel', function () { // do something }); var myInterval = $interval(function () { // do something in intervals }, 2584); var myTimeout = $timeout(function () { // defer some action here }, 1597); scope.domElement = element; $timeout(function () { // calling $destroy manually for testing purposes scope.$destroy(); }, 987); // here is where the cleanup happens scope.$on('$destroy', function () { // disable the listener rootModelListener(); // cancel the interval and timeout $interval.cancel(myInterval); $timeout.cancel(myTimeout); // nullify the DOM-bound model scope.domElement = null; }); element.on('$destroy', function () { // this is a jQuery event // clean up all vanilla JavaScript / jQuery artifacts here // respectful jQuery plugins have $destroy handlers, // that is the reason why this event is emitted... // follow the standards. }); };
! Λειτουργεί, ωστόσο, επειδή οποιαδήποτε συμβολοσειρά μεταβιβάστηκε σε $destroy
αξιολογείται με βάση το εύρος και $digest()
είναι διαθέσιμο στο πεδίο εφαρμογής. Αυτός είναι επίσης ο πιο συνηθισμένος τρόπος που τα χαρακτηριστικά παρακολουθούνται στις βασικές οδηγίες του AngularJS.
Δείτε τον κωδικό στο github για το πρότυπο και εξετάστε το {{ model }}
και
για ακόμη μεγαλύτερη αίσθηση.
Το AngularJS δουλεύει για λογαριασμό σας, αλλά όχι για όλους. Τα ακόλουθα πρέπει να καθαριστούν χειροκίνητα:
vastArray
ΕκδήλωσηΕάν δεν το κάνετε χειροκίνητα, θα αντιμετωπίσετε απροσδόκητες συμπεριφορές και διαρροές μνήμης. Ακόμα χειρότερα - αυτά δεν θα είναι άμεσα ορατά, αλλά τελικά θα υψωθούν. Ο νόμος του Μέρφι.
Εκπληκτικά, το AngularJS παρέχει εύχρηστους τρόπους αντιμετώπισης όλων αυτών:
item.velocity
Παρατηρήστε το jQuery {{ someModel }}
Εκδήλωση. Ονομάζεται όπως το AngularJS, αλλά αντιμετωπίζεται ξεχωριστά. Οι θεατές $ Scope δεν θα αντιδράσουν στο συμβάν jQuery.
Αυτό πρέπει να είναι πολύ απλό τώρα. Υπάρχει ένα πράγμα που πρέπει να καταλάβετε εδώ: ng-if
. Για κάθε δέσμευση ng-repeat
, το AngularJS δημιουργεί ένα πρόγραμμα παρακολούθησης. Σε κάθε φάση πέψης, κάθε τέτοια δέσμευση αξιολογείται και συγκρίνεται με την προηγούμενη τιμή. Αυτό ονομάζεται βρώμικος έλεγχος και αυτό κάνει το $ digest. Εάν η τιμή άλλαξε από τον τελευταίο έλεγχο, ενεργοποιείται η επιστροφή κλήσης του παρατηρητή. Εάν αυτή η επιστροφή κλήσης παρακολούθησης τροποποιήσει ένα μοντέλο (μεταβλητή εύρους $), ενεργοποιείται ένας νέος κύκλος εύρεσης $ (έως και 10 το πολύ) όταν πραγματοποιείται εξαίρεση.
Τα προγράμματα περιήγησης δεν έχουν προβλήματα ακόμη και με χιλιάδες δεσμεύσεις, εκτός εάν οι εκφράσεις είναι περίπλοκες. Η κοινή απάντηση για το «πόσα άτομα παρακολουθούν είναι εντάξει» είναι το 2000.
Λοιπόν, πώς μπορούμε να περιορίσουμε τον αριθμό των παρατηρητών; Μη παρακολουθώντας μοντέλα εμβέλειας όταν δεν περιμένουμε να αλλάξουν. Είναι αρκετά εύκολο και μετά από το AngularJS 1.3, δεδομένου ότι οι εφάπαξ συνδέσεις είναι πλέον πυρήνες.
$watch()
Μετά $Watchers
και $watch()
αξιολογούνται μία φορά, δεν θα αλλάξουν ποτέ ξανά. Μπορείτε ακόμα να εφαρμόσετε φίλτρα στον πίνακα, θα λειτουργούν μια χαρά. Είναι απλώς ότι ο ίδιος ο πίνακας δεν θα αξιολογηθεί. Σε πολλές περιπτώσεις, αυτή είναι μια νίκη.
Αυτό το σφάλμα AngularJS είχε ήδη καλυφθεί εν μέρει στα λάθη 9.β και στο 13. Αυτή είναι μια πιο λεπτομερής εξήγηση. Το AngularJS ενημερώνει το DOM ως αποτέλεσμα των λειτουργιών επανάκλησης στους παρατηρητές. Κάθε δεσμευτική, αυτή είναι η οδηγία $rootScope.$digest()
δημιουργεί παρατηρητές, αλλά οι θεατές έχουν επίσης ρυθμιστεί για πολλές άλλες οδηγίες όπως $scope
και $watch()
. Απλώς ρίξτε μια ματιά στον πηγαίο κώδικα, είναι πολύ ευανάγνωστο. Οι θεατές μπορούν επίσης να ρυθμιστούν χειροκίνητα και πιθανώς το έχετε κάνει τουλάχιστον μερικές φορές εσείς.
$digest()
ers δεσμεύονται σε πεδία. .$apply
μπορεί να λάβει συμβολοσειρές, οι οποίες αξιολογούνται με βάση το εύρος που το .$applyAsync
δεσμεύτηκε να. Μπορούν επίσης να αξιολογήσουν τις λειτουργίες. Και λαμβάνουν επίσης επιστροφές. Λοιπόν, όταν .$evalAsync
καλείται, όλα τα καταχωρημένα μοντέλα (δηλαδή $digest()
μεταβλητές) αξιολογούνται και συγκρίνονται με τις προηγούμενες τιμές τους. Εάν οι τιμές δεν ταιριάζουν, η επιστροφή στο $digest()
εκτελείται.
Είναι σημαντικό να καταλάβετε ότι, παρόλο που η τιμή ενός μοντέλου άλλαξε, η επιστροφή κλήσης δεν ενεργοποιείται μέχρι την επόμενη φάση πέψης. Ονομάζεται «φάση» για έναν λόγο - μπορεί να αποτελείται από διάφορους κύκλους χώνευσης. Εάν μόνο ένας παρατηρητής αλλάξει ένα μοντέλο εμβέλειας, εκτελείται ένας άλλος κύκλος χώνευσης.
Αλλά describe('some module', function () { it('should call the name-it service…', function () { // leave this empty for now }); ... });
δεν έχει δημοσκοπηθεί . Καλείται από βασικές οδηγίες, υπηρεσίες, μεθόδους κ.λπ. Εάν αλλάξετε ένα μοντέλο από μια προσαρμοσμένη συνάρτηση που δεν καλεί 'use strict'; var gulp = require('gulp'); var args = require('yargs').argv; var browserSync = require('browser-sync'); var karma = require('gulp-karma'); var protractor = require('gulp-protractor').protractor; var webdriverUpdate = require('gulp-protractor').webdriver_update; function test() { // Be sure to return the stream // NOTE: Using the fake './foobar' so as to run the files // listed in karma.conf.js INSTEAD of what was passed to // gulp.src ! return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'run' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); this.emit('end'); //instead of erroring the stream, end it }); } function tdd() { return gulp.src('./foobar') .pipe(karma({ configFile: 'test/karma.conf.js', action: 'start' })) .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero // console.log(err); // this.emit('end'); // not ending the stream here }); } function runProtractor () { var argument = args.suite || 'all'; // NOTE: Using the fake './foobar' so as to run the files // listed in protractor.conf.js, instead of what was passed to // gulp.src return gulp.src('./foobar') .pipe(protractor({ configFile: 'test/protractor.conf.js', args: ['--suite', argument] })) .on('error', function (err) { // Make sure failed tests cause gulp to exit non-zero throw err; }) .on('end', function () { // Close browser sync server browserSync.exit(); }); } gulp.task('tdd', tdd); gulp.task('test', test); gulp.task('test-e2e', ['webdriver-update'], runProtractor); gulp.task('webdriver-update', webdriverUpdate);
, console.log()
, debugInfo
ή οτιδήποτε άλλο που καλεί τελικά $(document.body).scope().$root
, οι δεσμεύσεις δεν θα ενημερωθούν.
Παρεμπιπτόντως, ο πηγαίος κώδικας για $($0).scope()
είναι πραγματικά πολύ περίπλοκο. Αξίζει ωστόσο να το διαβάσετε, καθώς τα ξεκαρδιστικά προειδοποιήσεις αντισταθμίζουν.
Εάν ακολουθήσετε τις τάσεις στην ανάπτυξη του front end και είστε λίγο τεμπέλης - όπως εγώ - τότε πιθανότατα προσπαθείτε να μην κάνετε τα πάντα με το χέρι. Παρακολούθηση όλων των εξαρτήσεων σας, επεξεργασία συνόλων αρχείων με διαφορετικούς τρόπους, επαναφόρτωση του προγράμματος περιήγησης μετά από κάθε αποθήκευση αρχείων - υπάρχουν πολλά περισσότερα να αναπτυχθούν παρά απλώς κωδικοποίηση.
Ίσως χρησιμοποιείτε bower και ίσως npm ανάλογα με τον τρόπο με τον οποίο εξυπηρετείτε την εφαρμογή σας. Υπάρχει πιθανότητα να χρησιμοποιείτε γκρίνια, γουλιά ή brunch. Ή bash, το οποίο είναι επίσης δροσερό. Στην πραγματικότητα, ίσως έχετε ξεκινήσει το τελευταίο σας έργο με κάποια γεννήτρια Yeoman!
Αυτό οδηγεί στην ερώτηση: καταλαβαίνετε όλη τη διαδικασία του τι πραγματικά κάνει η υποδομή σας; Χρειάζεστε αυτό που έχετε, ειδικά αν περάσατε ώρες προσπαθώντας να επιδιορθώσετε τη λειτουργικότητά σας με σύνδεση webserver;
Πάρτε ένα δευτερόλεπτο για να αξιολογήσετε τι χρειάζεστε. Όλα αυτά τα εργαλεία είναι μόνο εδώ για να σας βοηθήσουν, δεν υπάρχει άλλη ανταμοιβή για τη χρήση τους. Οι πιο έμπειροι προγραμματιστές που μιλάω τείνουν να απλοποιούν τα πράγματα.
Οι δοκιμές δεν θα κάνουν τον κώδικά σας απαλλαγμένο από μηνύματα σφάλματος AngularJS. Αυτό που θα κάνουν είναι να διαβεβαιώσουν ότι η ομάδα σας δεν αντιμετωπίζει προβλήματα παλινδρόμησης όλη την ώρα.
Γράφω συγκεκριμένα για τις δοκιμές μονάδας εδώ, όχι επειδή πιστεύω ότι είναι πιο σημαντικές από τις δοκιμές e2e, αλλά επειδή εκτελούν πολύ πιο γρήγορα. Πρέπει να παραδεχτώ ότι η διαδικασία που πρόκειται να περιγράψω είναι πολύ ευχάριστη.
Δοκιμή βάσει ανάπτυξης ως εφαρμογή για π.χ. gulp-karma runner, εκτελεί βασικά όλες τις δοκιμές μονάδας σας σε κάθε αποθήκευση αρχείου. Ο αγαπημένος μου τρόπος για να γράψω δοκιμές είναι, γράφω απλώς άδειες διαβεβαιώσεις πρώτα:
angular.reloadWithDebugInfo()
Μετά από αυτό, γράφω ή αναπαράγω τον πραγματικό κώδικα και μετά επιστρέφω στις δοκιμές και συμπληρώνω τις διαβεβαιώσεις με τον πραγματικό κωδικό δοκιμής.
Η εκτέλεση μιας εργασίας TDD σε ένα τερματικό επιταχύνει τη διαδικασία κατά περίπου 100%. Οι δοκιμές μονάδας εκτελούνται σε λίγα δευτερόλεπτα, ακόμα κι αν έχετε πολλά από αυτά. Απλώς αποθηκεύστε το δοκιμαστικό αρχείο και ο δρομέας θα το πάρει, θα αξιολογήσει τις δοκιμές σας και θα παράσχει σχόλια αμέσως.
Με τις δοκιμές e2e, η διαδικασία είναι πολύ πιο αργή. Η συμβουλή μου - χωρίστε τις δοκιμές e2e σε δοκιμαστικές σουίτες και εκτελέστε μία κάθε φορά. Το μοιρογνωμόνιο έχει υποστήριξη για αυτούς, και παρακάτω είναι ο κωδικός που χρησιμοποιώ για τις δοκιμαστικές εργασίες μου (μου αρέσει ο κόλπος).
var injector = $(document.body).injector(); var someService = injector.get('someService');
Α - σημεία διακοπής χρωμίου
Τα εργαλεία προγραμματιστή Chrome σάς επιτρέπουν να επισημαίνετε ένα συγκεκριμένο μέρος σε οποιοδήποτε από τα αρχεία που φορτώνονται στο πρόγραμμα περιήγησης, να διακόψετε την εκτέλεση κώδικα σε αυτό το σημείο και να σας επιτρέψουμε να αλληλεπιδράσετε με όλες τις διαθέσιμες μεταβλητές από αυτό το σημείο. Αυτό είναι πολύ! Αυτή η λειτουργικότητα δεν απαιτεί να προσθέσετε καθόλου κώδικα, όλα συμβαίνουν στα εργαλεία dev.
Όχι μόνο έχετε πρόσβαση σε όλες τις μεταβλητές, αλλά βλέπετε επίσης στοίβα κλήσεων, ίχνη στοίβας εκτύπωσης και άλλα. Μπορείτε ακόμη και να το ρυθμίσετε ώστε να λειτουργεί με ελαχιστοποιημένα αρχεία. Διαβάστε για αυτό εδώ .
Υπάρχουν άλλοι τρόποι με τους οποίους μπορείτε να έχετε παρόμοια πρόσβαση στο χρόνο εκτέλεσης, π.χ. προσθέτοντας Ng-init
κλήσεις. Αλλά τα σημεία διακοπής είναι πιο περίπλοκα.
Το AngularJS σάς επιτρέπει επίσης να έχετε πρόσβαση στο πεδίο εφαρμογής μέσω στοιχείων DOM (εφόσον είναι ενεργοποιημένο το ng-if
) και να κάνετε ένεση διαθέσιμων υπηρεσιών μέσω της κονσόλας. Εξετάστε τα ακόλουθα στην κονσόλα:
ng-repeat
ή δείξτε ένα στοιχείο στον επιθεωρητή και μετά:
var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });
Ακόμα κι αν το debugInfo δεν είναι ενεργοποιημένο, μπορείτε να κάνετε τα εξής:
var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }];
Και το έχετε διαθέσιμο μετά την επαναφόρτωση:
Για να κάνετε ένεση και να αλληλεπιδράσετε με μια υπηρεσία από την κονσόλα, δοκιμάστε:
$watch
B - χρονοδιάγραμμα χρωμίου
Ένα άλλο μεγάλο εργαλείο που συνοδεύει τα εργαλεία dev είναι το χρονοδιάγραμμα. Αυτό θα σας επιτρέψει να καταγράψετε και να αναλύσετε τη ζωντανή απόδοση της εφαρμογής σας καθώς τη χρησιμοποιείτε. Η έξοδος δείχνει, μεταξύ άλλων, τη χρήση μνήμης, το ρυθμό καρέ και την ανατομή των διαφορετικών διαδικασιών που απασχολούν τη CPU: φόρτωση, δέσμες ενεργειών, απόδοση και ζωγραφική.
Εάν διαπιστώσετε ότι η απόδοση της εφαρμογής σας υποβαθμίζεται, πιθανότατα θα μπορείτε να βρείτε την αιτία για αυτό μέσω της καρτέλας χρονολογίου. Απλώς καταγράψτε τις ενέργειές σας που οδήγησαν σε ζητήματα απόδοσης και δείτε τι συμβαίνει. Πάρα πολλοί θεατές; Θα δείτε κίτρινες μπάρες να παίρνουν πολύ χώρο. Διαρροές μνήμης; Μπορείτε να δείτε πόση μνήμη καταναλώθηκε με την πάροδο του χρόνου σε ένα γράφημα.
Λεπτομερής περιγραφή: https://developer.chrome.com/devtools/docs/timeline
Γ - επιθεώρηση εφαρμογών από απόσταση σε iOS και Android
Εάν αναπτύσσετε μια υβριδική εφαρμογή ή μια αποκριτική εφαρμογή ιστού, μπορείτε να αποκτήσετε πρόσβαση στην κονσόλα της συσκευής σας, στο δέντρο DOM και σε όλα τα άλλα εργαλεία που είναι διαθέσιμα είτε μέσω των εργαλείων Chrome ή Safari dev. Αυτό περιλαμβάνει το WebView και το UIWebView.
Αρχικά, ξεκινήστε τον διακομιστή ιστού σας στον κεντρικό υπολογιστή 0.0.0.0 έτσι ώστε να είναι προσβάσιμος από το τοπικό σας δίκτυο. Ενεργοποίηση επιθεωρητή ιστού στις ρυθμίσεις. Στη συνέχεια, συνδέστε τη συσκευή σας στην επιφάνεια εργασίας σας και αποκτήστε πρόσβαση στη σελίδα τοπικής ανάπτυξης, χρησιμοποιώντας το ip του μηχανήματός σας αντί του κανονικού 'localhost'. Αυτό είναι το μόνο που χρειάζεται, η συσκευή σας θα πρέπει πλέον να είναι διαθέσιμη από το πρόγραμμα περιήγησης της επιφάνειας εργασίας σας.
Εδώ είναι οι αναλυτικές οδηγίες για το Android Και για iOS, οι ανεπίσημοι οδηγοί μπορούν να βρεθούν εύκολα μέσω του google.
Πρόσφατα είχα μια υπέροχη εμπειρία με browserSync . Λειτουργεί με παρόμοιο τρόπο με το livereload, αλλά στην πραγματικότητα συγχρονίζει όλα τα προγράμματα περιήγησης που βλέπουν την ίδια σελίδα μέσω του browserSync. Αυτό περιλαμβάνει την αλληλεπίδραση των χρηστών, όπως κύλιση, κλικ σε κουμπιά κ.λπ. Έβλεπα την έξοδο καταγραφής της εφαρμογής iOS, ενώ ελέγχω τη σελίδα στο iPad από την επιφάνεια εργασίας μου. Δούλεψε ωραία!
// when in doubt, comment it out! :)
, από τον ήχο του, θα πρέπει να είναι παρόμοια με
|_+_|και
|_+_|, σωστά; Αναρωτηθήκατε ποτέ γιατί υπάρχει ένα σχόλιο στα έγγραφα ότι δεν πρέπει να χρησιμοποιείται; IMHO που ήταν εκπληκτικό! Θα περίμενα η οδηγία να αρχικοποιήσει ένα μοντέλο. Αυτό είναι και αυτό που κάνει, αλλά… εφαρμόζεται με διαφορετικό τρόπο, δηλαδή δεν παρακολουθεί την τιμή του χαρακτηριστικού. Δεν χρειάζεται να περιηγηθείτε στον πηγαίο κώδικα του AngularJS - επιτρέψτε μου να τον φέρω:
|_+_|
Λιγότερο από ό, τι θα περίμενε κανείς; Πολύ ευανάγνωστο, εκτός από την περίεργη συντακτική οδηγία, έτσι δεν είναι; Η έκτη γραμμή είναι το θέμα.
Συγκρίνετέ το με το ng-show:
|_+_|
Και πάλι, η έκτη γραμμή. Υπάρχει ένα
|_+_|εκεί, αυτό κάνει την οδηγία δυναμική. Στον πηγαίο κώδικα AngularJS, μεγάλο μέρος του κώδικα είναι σχόλια που περιγράφουν κώδικα που ήταν κυρίως αναγνώσιμος από την αρχή. Πιστεύω ότι είναι ένας πολύ καλός τρόπος για να μάθετε για το AngularJS.
Αυτός ο οδηγός που καλύπτει τα πιο συνηθισμένα λάθη του AngularJS είναι σχεδόν διπλάσιος από τους άλλους οδηγούς. Αποδείχθηκε έτσι φυσικά. Η ζήτηση για υψηλής ποιότητας μηχανικούς διεπαφής JavaScript είναι πολύ υψηλή. Το AngularJS είναι τόσο ζεστό τώρα , και διατηρεί μια σταθερή θέση ανάμεσα στα πιο δημοφιλή εργαλεία ανάπτυξης για μερικά χρόνια. Με το AngularJS 2.0 στο δρόμο, πιθανότατα θα κυριαρχήσει για τα επόμενα χρόνια.
Αυτό που είναι υπέροχο για την ανάπτυξη front-end είναι ότι είναι πολύ ικανοποιητικό. Η δουλειά μας είναι ορατή αμέσως και οι άνθρωποι αλληλεπιδρούν άμεσα με τα προϊόντα που παραδίδουμε. Ο χρόνος που ξοδεύτηκε μαθαίνοντας JavaScript , και πιστεύω ότι πρέπει να επικεντρωθούμε στη γλώσσα JavaScript, είναι μια πολύ καλή επένδυση. Είναι η γλώσσα του Διαδικτύου. Ο ανταγωνισμός είναι εξαιρετικά δυνατός! Υπάρχει μια εστίαση για εμάς - η εμπειρία χρήστη. Για να είμαστε επιτυχημένοι, πρέπει να καλύψουμε τα πάντα.
Μπορείτε να κατεβάσετε τον πηγαίο κώδικα που χρησιμοποιείται σε αυτά τα παραδείγματα GitHub . Μη διστάσετε να το κατεβάσετε και να το κάνετε δικό σας.
Ήθελα να δώσω πιστώσεις σε τέσσερις προγραμματιστές εκδόσεων που με ενέπνευσαν περισσότερο:
Θα ήθελα επίσης να ευχαριστήσω όλους τους υπέροχους ανθρώπους στα κανάλια FreeNode #angularjs και #javascript για πολλές εξαιρετικές συνομιλίες και συνεχή υποστήριξη.
Και τέλος, να θυμάστε πάντα:
|_+_|
Εάν ακολουθήσετε τις τάσεις στην ανάπτυξη του front end και είστε λίγο τεμπέλης - όπως εγώ - τότε πιθανότατα προσπαθείτε να μην κάνετε τα πάντα με το χέρι. Παρακολούθηση όλων των εξαρτήσεων σας, επεξεργασία συνόλων αρχείων με διαφορετικούς τρόπους, επαναφόρτωση του προγράμματος περιήγησης μετά από κάθε αποθήκευση αρχείων - υπάρχουν πολλά περισσότερα να αναπτυχθούν παρά απλώς κωδικοποίηση.
Ίσως χρησιμοποιείτε bower και ίσως npm ανάλογα με τον τρόπο με τον οποίο εξυπηρετείτε την εφαρμογή σας. Υπάρχει πιθανότητα να χρησιμοποιείτε γκρίνια, γουλιά ή brunch. Ή bash, το οποίο είναι επίσης δροσερό. Στην πραγματικότητα, ίσως έχετε ξεκινήσει το τελευταίο σας έργο με κάποια γεννήτρια Yeoman!
Αυτό οδηγεί στην ερώτηση: καταλαβαίνετε όλη τη διαδικασία του τι πραγματικά κάνει η υποδομή σας; Χρειάζεστε αυτό που έχετε, ειδικά αν περάσατε ώρες προσπαθώντας να επιδιορθώσετε τη λειτουργικότητά σας με σύνδεση webserver;
Πάρτε ένα δευτερόλεπτο για να αξιολογήσετε τι χρειάζεστε. Όλα αυτά τα εργαλεία είναι μόνο εδώ για να σας βοηθήσουν, δεν υπάρχει άλλη ανταμοιβή για τη χρήση τους. Οι πιο έμπειροι προγραμματιστές που μιλάω τείνουν να απλοποιούν τα πράγματα.
Οι δοκιμές δεν θα κάνουν τον κώδικά σας απαλλαγμένο από μηνύματα σφάλματος AngularJS. Αυτό που θα κάνουν είναι να διαβεβαιώσουν ότι η ομάδα σας δεν αντιμετωπίζει προβλήματα παλινδρόμησης όλη την ώρα.
Γράφω συγκεκριμένα για τις δοκιμές μονάδας εδώ, όχι επειδή πιστεύω ότι είναι πιο σημαντικές από τις δοκιμές e2e, αλλά επειδή εκτελούν πολύ πιο γρήγορα. Πρέπει να παραδεχτώ ότι η διαδικασία που πρόκειται να περιγράψω είναι πολύ ευχάριστη.
Δοκιμή βάσει ανάπτυξης ως εφαρμογή για π.χ. gulp-karma runner, εκτελεί βασικά όλες τις δοκιμές μονάδας σας σε κάθε αποθήκευση αρχείου. Ο αγαπημένος μου τρόπος για να γράψω δοκιμές είναι, γράφω απλώς άδειες διαβεβαιώσεις πρώτα:
angular.reloadWithDebugInfo()
Μετά από αυτό, γράφω ή αναπαράγω τον πραγματικό κώδικα και μετά επιστρέφω στις δοκιμές και συμπληρώνω τις διαβεβαιώσεις με τον πραγματικό κωδικό δοκιμής.
Η εκτέλεση μιας εργασίας TDD σε ένα τερματικό επιταχύνει τη διαδικασία κατά περίπου 100%. Οι δοκιμές μονάδας εκτελούνται σε λίγα δευτερόλεπτα, ακόμα κι αν έχετε πολλά από αυτά. Απλώς αποθηκεύστε το δοκιμαστικό αρχείο και ο δρομέας θα το πάρει, θα αξιολογήσει τις δοκιμές σας και θα παράσχει σχόλια αμέσως.
Με τις δοκιμές e2e, η διαδικασία είναι πολύ πιο αργή. Η συμβουλή μου - χωρίστε τις δοκιμές e2e σε δοκιμαστικές σουίτες και εκτελέστε μία κάθε φορά. Το μοιρογνωμόνιο έχει υποστήριξη για αυτούς, και παρακάτω είναι ο κωδικός που χρησιμοποιώ για τις δοκιμαστικές εργασίες μου (μου αρέσει ο κόλπος).
var injector = $(document.body).injector(); var someService = injector.get('someService');
Α - σημεία διακοπής χρωμίου
Τα εργαλεία προγραμματιστή Chrome σάς επιτρέπουν να επισημαίνετε ένα συγκεκριμένο μέρος σε οποιοδήποτε από τα αρχεία που φορτώνονται στο πρόγραμμα περιήγησης, να διακόψετε την εκτέλεση κώδικα σε αυτό το σημείο και να σας επιτρέψουμε να αλληλεπιδράσετε με όλες τις διαθέσιμες μεταβλητές από αυτό το σημείο. Αυτό είναι πολύ! Αυτή η λειτουργικότητα δεν απαιτεί να προσθέσετε καθόλου κώδικα, όλα συμβαίνουν στα εργαλεία dev.
Όχι μόνο έχετε πρόσβαση σε όλες τις μεταβλητές, αλλά βλέπετε επίσης στοίβα κλήσεων, ίχνη στοίβας εκτύπωσης και άλλα. Μπορείτε ακόμη και να το ρυθμίσετε ώστε να λειτουργεί με ελαχιστοποιημένα αρχεία. Διαβάστε για αυτό εδώ .
Υπάρχουν άλλοι τρόποι με τους οποίους μπορείτε να έχετε παρόμοια πρόσβαση στο χρόνο εκτέλεσης, π.χ. προσθέτοντας Ng-init
κλήσεις. Αλλά τα σημεία διακοπής είναι πιο περίπλοκα.
Το AngularJS σάς επιτρέπει επίσης να έχετε πρόσβαση στο πεδίο εφαρμογής μέσω στοιχείων DOM (εφόσον είναι ενεργοποιημένο το ng-if
) και να κάνετε ένεση διαθέσιμων υπηρεσιών μέσω της κονσόλας. Εξετάστε τα ακόλουθα στην κονσόλα:
ng-repeat
ή δείξτε ένα στοιχείο στον επιθεωρητή και μετά:
var ngInitDirective = ngDirective({ priority: 450, compile: function() { return { pre: function(scope, element, attrs) { scope.$eval(attrs.ngInit); } }; } });
Ακόμα κι αν το debugInfo δεν είναι ενεργοποιημένο, μπορείτε να κάνετε τα εξής:
var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }];
Και το έχετε διαθέσιμο μετά την επαναφόρτωση:
Για να κάνετε ένεση και να αλληλεπιδράσετε με μια υπηρεσία από την κονσόλα, δοκιμάστε:
$watch
B - χρονοδιάγραμμα χρωμίου
Ένα άλλο μεγάλο εργαλείο που συνοδεύει τα εργαλεία dev είναι το χρονοδιάγραμμα. Αυτό θα σας επιτρέψει να καταγράψετε και να αναλύσετε τη ζωντανή απόδοση της εφαρμογής σας καθώς τη χρησιμοποιείτε. Η έξοδος δείχνει, μεταξύ άλλων, τη χρήση μνήμης, το ρυθμό καρέ και την ανατομή των διαφορετικών διαδικασιών που απασχολούν τη CPU: φόρτωση, δέσμες ενεργειών, απόδοση και ζωγραφική.
Εάν διαπιστώσετε ότι η απόδοση της εφαρμογής σας υποβαθμίζεται, πιθανότατα θα μπορείτε να βρείτε την αιτία για αυτό μέσω της καρτέλας χρονολογίου. Απλώς καταγράψτε τις ενέργειές σας που οδήγησαν σε ζητήματα απόδοσης και δείτε τι συμβαίνει. Πάρα πολλοί θεατές; Θα δείτε κίτρινες μπάρες να παίρνουν πολύ χώρο. Διαρροές μνήμης; Μπορείτε να δείτε πόση μνήμη καταναλώθηκε με την πάροδο του χρόνου σε ένα γράφημα.
Λεπτομερής περιγραφή: https://developer.chrome.com/devtools/docs/timeline
Γ - επιθεώρηση εφαρμογών από απόσταση σε iOS και Android
Εάν αναπτύσσετε μια υβριδική εφαρμογή ή μια αποκριτική εφαρμογή ιστού, μπορείτε να αποκτήσετε πρόσβαση στην κονσόλα της συσκευής σας, στο δέντρο DOM και σε όλα τα άλλα εργαλεία που είναι διαθέσιμα είτε μέσω των εργαλείων Chrome ή Safari dev. Αυτό περιλαμβάνει το WebView και το UIWebView.
Αρχικά, ξεκινήστε τον διακομιστή ιστού σας στον κεντρικό υπολογιστή 0.0.0.0 έτσι ώστε να είναι προσβάσιμος από το τοπικό σας δίκτυο. Ενεργοποίηση επιθεωρητή ιστού στις ρυθμίσεις. Στη συνέχεια, συνδέστε τη συσκευή σας στην επιφάνεια εργασίας σας και αποκτήστε πρόσβαση στη σελίδα τοπικής ανάπτυξης, χρησιμοποιώντας το ip του μηχανήματός σας αντί του κανονικού 'localhost'. Αυτό είναι το μόνο που χρειάζεται, η συσκευή σας θα πρέπει πλέον να είναι διαθέσιμη από το πρόγραμμα περιήγησης της επιφάνειας εργασίας σας.
Εδώ είναι οι αναλυτικές οδηγίες για το Android Και για iOS, οι ανεπίσημοι οδηγοί μπορούν να βρεθούν εύκολα μέσω του google.
Πρόσφατα είχα μια υπέροχη εμπειρία με browserSync . Λειτουργεί με παρόμοιο τρόπο με το livereload, αλλά στην πραγματικότητα συγχρονίζει όλα τα προγράμματα περιήγησης που βλέπουν την ίδια σελίδα μέσω του browserSync. Αυτό περιλαμβάνει την αλληλεπίδραση των χρηστών, όπως κύλιση, κλικ σε κουμπιά κ.λπ. Έβλεπα την έξοδο καταγραφής της εφαρμογής iOS, ενώ ελέγχω τη σελίδα στο iPad από την επιφάνεια εργασίας μου. Δούλεψε ωραία!
// when in doubt, comment it out! :)
, από τον ήχο του, θα πρέπει να είναι παρόμοια με
|_+_|και
|_+_|, σωστά; Αναρωτηθήκατε ποτέ γιατί υπάρχει ένα σχόλιο στα έγγραφα ότι δεν πρέπει να χρησιμοποιείται; IMHO που ήταν εκπληκτικό! Θα περίμενα η οδηγία να αρχικοποιήσει ένα μοντέλο. Αυτό είναι και αυτό που κάνει, αλλά… εφαρμόζεται με διαφορετικό τρόπο, δηλαδή δεν παρακολουθεί την τιμή του χαρακτηριστικού. Δεν χρειάζεται να περιηγηθείτε στον πηγαίο κώδικα του AngularJS - επιτρέψτε μου να τον φέρω:
|_+_|
Λιγότερο από ό, τι θα περίμενε κανείς; Πολύ ευανάγνωστο, εκτός από την περίεργη συντακτική οδηγία, έτσι δεν είναι; Η έκτη γραμμή είναι το θέμα.
Συγκρίνετέ το με το ng-show:
|_+_|
Και πάλι, η έκτη γραμμή. Υπάρχει ένα
|_+_|εκεί, αυτό κάνει την οδηγία δυναμική. Στον πηγαίο κώδικα AngularJS, μεγάλο μέρος του κώδικα είναι σχόλια που περιγράφουν κώδικα που ήταν κυρίως αναγνώσιμος από την αρχή. Πιστεύω ότι είναι ένας πολύ καλός τρόπος για να μάθετε για το AngularJS.
Αυτός ο οδηγός που καλύπτει τα πιο συνηθισμένα λάθη του AngularJS είναι σχεδόν διπλάσιος από τους άλλους οδηγούς. Αποδείχθηκε έτσι φυσικά. Η ζήτηση για υψηλής ποιότητας μηχανικούς διεπαφής JavaScript είναι πολύ υψηλή. Το AngularJS είναι τόσο ζεστό τώρα , και διατηρεί μια σταθερή θέση ανάμεσα στα πιο δημοφιλή εργαλεία ανάπτυξης για μερικά χρόνια. Με το AngularJS 2.0 στο δρόμο, πιθανότατα θα κυριαρχήσει για τα επόμενα χρόνια.
Αυτό που είναι υπέροχο για την ανάπτυξη front-end είναι ότι είναι πολύ ικανοποιητικό. Η δουλειά μας είναι ορατή αμέσως και οι άνθρωποι αλληλεπιδρούν άμεσα με τα προϊόντα που παραδίδουμε. Ο χρόνος που ξοδεύτηκε μαθαίνοντας JavaScript , και πιστεύω ότι πρέπει να επικεντρωθούμε στη γλώσσα JavaScript, είναι μια πολύ καλή επένδυση. Είναι η γλώσσα του Διαδικτύου. Ο ανταγωνισμός είναι εξαιρετικά δυνατός! Υπάρχει μια εστίαση για εμάς - η εμπειρία χρήστη. Για να είμαστε επιτυχημένοι, πρέπει να καλύψουμε τα πάντα.
διαφορά σε c corp και s corp
Μπορείτε να κατεβάσετε τον πηγαίο κώδικα που χρησιμοποιείται σε αυτά τα παραδείγματα GitHub . Μη διστάσετε να το κατεβάσετε και να το κάνετε δικό σας.
Ήθελα να δώσω πιστώσεις σε τέσσερις προγραμματιστές εκδόσεων που με ενέπνευσαν περισσότερο:
Θα ήθελα επίσης να ευχαριστήσω όλους τους υπέροχους ανθρώπους στα κανάλια FreeNode #angularjs και #javascript για πολλές εξαιρετικές συνομιλίες και συνεχή υποστήριξη.
Και τέλος, να θυμάστε πάντα:
|_+_|