Mark Meyer is a full stack software engineer with over a year of angular.js experience in real-world development. Mark has developed a wide range of languages, from C-based server applications to rails-based Web applications to iOS apps developed with Swift.
Introduction Angularjs is one of the most popular JavaScript frameworks, and one of Angularjs's goals is to simplify the development process, which makes it very good at building small app prototypes, but it can also be used to develop fully functional client applications. Easy to develop, with a wide range of features and superior performance to make it widely available, however, a large number of common pitfalls follow. The following list extracts some of the common angularjs errors, especially when the app is expanding in size.
1. The MVC directory structure Angularjs, accurately, is an MVC framework. Its model does not have a clear division of labor as Backbone.js, but its architecture still complements each other. When developing with an MVC framework, it is common practice to categorize it by file type:
templates/
_login.html
_feed.html
app/
App.js
controllers/
Logincontroller.js
Feedcontroller.js
directives/
Feedentrydirective.js
services/
Loginservice.js
Feedservice.js
filters/
Capatalizefilter.js
This seems to be an obvious directory structure, especially if rails is doing the same. However, once the app size starts to expand, this structure will cause a lot of directories to open at once. Whether using Sublime,visual studio or vim combined with nerd tree, it takes a lot of time to scroll up and down in the directory tree.
Do not organize files by type, but by attributes:
app/
App.js
feed/
_feed.html
Feedcontroller.js
Feedentrydirective.js
Feedservice.js
login/
_login.html
Logincontroller.js
Loginservice.js
shared/
Capatalizefilter.js
This directory structure makes it easier for us to find files related to a particular feature, thus speeding up development progress. Putting. html and. js files together can be controversial, but the time saved is more valuable.
2. It is common for the module (or no module) to start by placing everything under the main module. For small apps, there was no problem at first, but it quickly became unmanageable.
var app = Angular.module (' app ', []), App.service (' MyService ', function () { //service code}); App.controller (' Myctrl ', function ($scope, myservice) { //controller code});
After this, the usual approach is to organize the code by object type.
var services = angular.module (' Services ', []); Services.service (' MyService ', function () { //service code}); var controllers = Angular.module (' Controllers ', [' services ']); Controllers.controller (' Myctrl ', function ($scope, MyService) { //controller code}); var app = Angular.module (' app ', [' Controllers ', ' services ']);
This approach is similar to the directory structure mentioned in the first part of the previous section: not good enough. Similarly, they can be divided by attributes and easily extended.
var sharedservicesmodule = angular.module (' SharedServices ', []); Sharedservices.service (' NetworkService ', function ($ HTTP) {}); var loginmodule = angular.module (' login ', [' sharedservices ']); Loginmodule.service (' Loginservice ', function ( NetworkService) {}); Loginmodule.controller (' Loginctrl ', function ($scope, loginservice) {}); var app = Angular.module (' app ', [' sharedservices ', ' login ');
When developing a large application, it is not possible to include everything on a single page, and partitioning modules by attributes makes it easier to reuse modules across applications.
3. Dependency Injection Dependency Injection is one of the best patterns in Angularjs, making testing simpler and more dependent on relationships. The ANGULARJS is very flexible to inject, and the simplest way is simply to pass the name of the dependent person into the function of the module:
var app = Angular.module (' app ', []); App.controller (' Mainctrl ', function ($scope, $timeout) { $timeout (function () { console.log ($scope); }, (1000);});
Here, it is clear that Mainctrl relies on $scope and $timeout.
Everything is fine until you are ready to deploy it to the production environment and compress the code. But if you use UGLIFYJS, the previous example would look like this:
var app=angular.module ("App", []);
App.controller ("Mainctrl", function (e,t) {T (function () {Console.log (e)},1e3)})
Now how Angularjs know who Mainctrl depends on? ANGULARJS provides a very simple workaround that will depend on passing in as a string array, with a function at the end of the array, with all dependencies as its arguments.
App.controller (' Mainctrl ', [' $scope ', ' $timeout ', function ($scope, $timeout) { $timeout (function () { Console.log ($scope); }, 1000);}]);
When you compress code like this, Angularjs can clearly know how to interpret these dependencies:
App.controller ("Mainctrl", ["$scope", "$timeout", function (e,t) {T (function () {Console.log (e)},1e3)}])
3.1 Global dependencies when writing ANGULARJS applications, it is common to bind a dependent object to the global scope. This means that this dependency is available in any ANGULARJS code, but it destroys the dependency injection model and causes some problems, especially when testing.
These global objects can be easily encapsulated into modules using ANGULARJS, allowing them to be injected into the module like the ANGULARJS standard module.
Underscrore.js is a great library that simplifies JavaScript code in a functional style, and you can convert it into a module in the following ways:
var underscore = Angular.module (' underscore ', []) underscore.factory (' _ ', function () { return window._;// Underscore must already is loaded on the page}); var app = Angular.module (' app ', [' underscore ']); App.controller (' Mainctrl ', [' $scope ', ' _ ', function ($scope, _) { init = function () { _.keys ($scope); } Init ();}]);
This allows the application to continue to develop in a angularjs dependency injection style, while also replacing underscore in the test phase.
This may seem trivial and unnecessary, but if your code is using use strict (and should be used!). ), then this is necessary.
4. Controller Expander Controller is the most basic part of ANGULARJS. Accidentally putting too much logic into it, especially at the beginning. The controller should never operate the DOM or hold the DOM selector, which is where directive and Ng-model are. Similarly, the business logic should exist in the service, not the controller.
The data should also be stored in the service, unless they are already tied to the $scope. The service itself is singleton and exists throughout the application's lifecycle, but the controller is transient between the states of the application. If the data is stored in the controller, it needs to be retrieved from somewhere again when it is instantiated again. Even if you store the data in Localstorage, you get the same level of speed as you would from a JavaScript variable.
ANGULARJS works well when it follows a single responsibility principle (SRP). If the controller is the coordinator between the view and the model, it should contain as few logic as possible. This will also make testing easier.
5. Service vs Factory These two nouns are confusing to almost every ANGULARJS developer at the beginning of a beginner's day. This is really not a good thing, because they are (almost) the same grammatical sugar!
The following are their definitions in the Angularjs source code:
function factory (name, FACTORYFN) { return provider (name, {$get: factoryfn}), function service (name, constructor) { return factory (name, [' $injector ', function ($injector) { return $injector. Instantiate (constructor); }]);}
As you can see from the source code, the service simply calls the factory function, and factory calls the provider function. In fact, Angularjs also offers some additional provider packages, such as value,constant and decorator. But these do not lead to a similar degree of confusion, and their documentation is quite clear in the application scenario.
Since the service simply calls the factory function, what is the difference between the two? Clues in $injector.instantiate; in this function, $injector creates a new instance in the service's constructor.
Here's an example of how a service and a factory do the same thing:
var app = Angular.module (' app ', []); App.service (' HelloWorldService ', function () { This.hello = function () { return "Hello World"; };}); App.factory (' Helloworldfactory ', function () { return { hello:function () { return "Hello World"; }} );
When HelloWorldService or helloworldfactory are injected into the controller, they all have a Hello method that returns "Hello World". The service's constructor is instantiated at the time of Declaration, and the factory object is passed once each time it is injected, but there is still only one factory instance.
Since you can do the same thing, why do you need two different styles? This provides more flexibility than service,factory because it can return functions, which can then be new. This follows the factory pattern in object-oriented programming, where the factory is an object that can create other objects.
App.factory (' Hellofactory ', function () { return function (name) { this.name = name; This.hello = function () { return "Hello" + THIS.name;};};} );
Here is an example of a controller using the service and two factory. Hellofactory returns a function that sets the value of name when it is new.
App.controller (' Helloctrl ', function ($scope, HelloWorldService, Helloworldfactory, hellofactory) { init = function () { Helloworldservice.hello ();//' Hello World ' helloworldfactory.hello ();//' Hello World ' new Hellofactory (' Readers '). Hello ()//' Hello Readers ' } init ();});
At the outset, it is best to use only the service.
Factory is also useful when designing a class that contains many private methods:
App.factory (' Privatefactory ', function () { var privatefunc = function (name) { return Name.split (""). Reverse () . Join (""); Reverses the name }; return { hello:function (name) { return "Hello" + privatefunc (name); } });
This example allows Privatefactory's public API to have no direct access to Privatefunc, which can be done in the service, but is more intuitive in factory.
6. Not using Batarangbatarang is an excellent chrome plugin for developing and debugging ANGULARJS applications.
Batarang provides the ability to browse the model, allowing you to observe how ANGULARJS internally determines the model that is bound to the scope. This is useful when observing the binding values of Directive and isolate scopes.
The Batarang also provides a dependency graph. If you use an untested code base, this dependency graph can be used to determine which services should be focused.
Finally, Batarang provides performance analysis. Angularjs performance is good, but for applications with more and more custom directive and complex logic, sometimes it doesn't seem smooth enough. Using the Batarang Performance tool, it is easy to see which function is the longest running time in a digest cycle. This tool can also show a complete watch tree, which is useful when there are many watcher.
7. Excessive watcher mentioned in the previous point, Angularjs performance is good. Since the dirty data check is done in a digest cycle, the cycle can have significant performance problems once the number of watcher increases to about 2000. (2000 this number does not necessarily cause a significant decrease in performance, but this is a good experience.) In the Angularjs 1.3 release version, there have been some changes that allow strict control over the digest cycle, and Aaron Gray has a very good article on this issue. )
The following "Execute function expression (iife)" will print out the number of watcher on the current page, and you can simply paste it into the console and observe the results. This section of Iife originates from Jared's answer on StackOverflow:
(function () { var root = $ (document.getelementsbytagname (' body ')); var watchers = []; var f = function (Element) { if (Element.data (). hasOwnProperty (' $scope ')) { Angular.foreach (Element.data ()). $ scope.$ $watchers, function (watcher) { watchers.push (watcher); }); Angular.foreach (Element.children (), function (childelement) { f ($ (childelement));} ); f (root); Console.log (watchers.length);}) ();
By this way get the number of watcher, combined with the Batarang performance part of the Watch tree, you should be able to find where duplicate code exists, or which unchanging data has watch.
When there are unchanging data, and you want to use Angularjs to template it, you can consider using Bindonce. Bindonce is a simple instruction that allows you to use a template in Angularjs, but it does not increase watch and avoids the increase in watch count.
8. Determine the scope of $scope JavaScript prototype-based inheritance has a subtle difference from class-based inheritance in object-oriented. This is usually not a problem, but it is shown when using $scope. In Angularjs, each $scope inherits the parent $scope, and the highest level is $rootscope. ($scope are somewhat different when used in directive, isolate scopes only inherit explicitly declared properties.) )
Because of the characteristics of prototype inheritance, it is not important to share data between the parent class and the subclass. But if you're not careful, it's easy to override the parent $scope property.
For example, we need to display a user name on a navigation bar, which is entered in the login form. The following attempt should be able to work:
<div ng-controller= "Navctrl" > <span>{{user}}</span> <div ng-controller= "Loginctrl" > <span>{{user}}</span> <input ng-model= "user" ></input> </div>< /div>
Question: In text input, set the user's Ng-model, which template will be updated when the user enters the content? Navctrl or Loginctrl, or will it be?
If you choose Loginctrl, you may already understand how prototype inheritance works.
The prototype chain does not work when you retrieve the literal value. It is necessary to retrieve the prototype chain if the Navctrl is also updated, but this occurs if the value is an object. (Remember, in JavaScript, functions, arrays, and objects are objects)
So in order to get the expected behavior, you need to create an object in Navctrl, which can be referenced by Loginctrl.
<div ng-controller= "Navctrl" > <span>{{user.name}}</span> <div ng-controller= " Loginctrl "> <span>{{user.name}}</span> <input ng-model=" User.Name "></input> </div></div>
Now, because user is an object, the prototype chain will work, and the Navctrl template and $scope are updated with Loginctrl.
This example is somewhat reluctant, but when you use directive to create sub-$scope, such as ngrepeat, the problem is easily generated.
9. Manual testing because TDD may not be a development method that every developer likes, they do manual testing when the developer checks whether the code is working or if it affects anything else.
It makes no sense to not test the ANGULARJS application. The design of the ANGULARJS makes it fully measurable. The dependency injection and Ngmock modules are proof. The ANGULARJS core team has developed a number of tools that can make testing a higher level.
9.1 Protractor Unit Testing is the basis of a test set, but integration testing is forced to be more realistic given the increasing complexity of the app. Fortunately, Angularjs's core team has provided the necessary tools.
We have built protractor, an end-to-end tester for simulating user interaction, that can help you verify the health of your ANGULARJS program.
Protractor uses the Jasmine test framework to define tests, and protractor has a very robust API for different page interaction behaviors.
There are other end-to-end test tools, but the advantage of protractor is the ability to understand how to work with ANGULARJS code, especially during the $digest cycle.
9.2 Karma Once we have finished writing the integration test with protractor, the next step is to execute the test. Waiting for test execution, especially integration testing, is a pain for every developer. Angularjs's core team was sympathetic and developed karma.
Karma is a JavaScript test execution tool that helps to work with closed loops. Karma is able to do this because it runs tests when the specified file is changed. Karma can also perform tests in parallel on multiple browsers. Different devices can also point to Karma servers to better cover real-world scenarios.
10. Using jquery
jquery is a magical library that standardizes cross-platform development and is almost a necessity for modern web development. But despite the many outstanding features of jquery, its philosophy and angularjs are not consistent.
Angularjs is a framework for building applications, while jquery is a library that simplifies "HTML document traversal and manipulation, event handling, animation, and Ajax." This is the most basic difference between the two, Angularjs is dedicated to the architecture of the application, regardless of the HTML page.
In order to better understand how to establish a ANGULARJS program, please
stop using jquery。 jquery lets developers think about existing HTML standards, but as the document says, "ANGULARJS allows you to extend the HTML glossary for your app."
Dom operations should only be done in directive, but that doesn't mean they can only be encapsulated in jquery. Before you use jquery, you should think about whether this feature is angularjs already provided. When directives is combined, it can create powerful tools that work quite well.
This day may come, a great jquery becomes necessary, but introducing it at the outset is a common mistake.
Conclusion Angularjs is a great framework for continuous evolution in the community. The customary angularjs is still an evolving concept, but hopefully, by following the conventions mentioned above, you can avoid some of the big pitfalls of developing large-scale ANGULARJS applications.
Original link: https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make
10 errors that ANGULARJS developers often make