10 Most Common AngularJS Mistakes Every Developer Should Avoid

Updated on :October 05, 2023
By :Sushmita Sen

AngularJS is a popular programming language and is widely used by developers all across the globe. They aim to provide the finest experience to the users by designing the applications accordingly. However, during this quest to provide users with the best application, they often commit mistakes and errors. 

Mistakes that Angular JS Developers Must Avoid

So what are those common mistakes that AngularJS developers should avoid? Let’s find out. 

MISTAKE-1: Making Use of jQuery

Most of the experienced jQuery developers have a habit of using jQuery with AngularJS, which is not the right approach. They do so because they have a good understanding and knowledge of the abstract and concise code of jQuery and just don’t want to get rid of this habit even while using AngularJS. But what developers don’t realize is that AngularJS has much better in-house provisions that eliminate the need for using jQuery. 

So, let’s explore the two reasons why developers should rectify the mistake of blending jQuery with AngularJS. 

#1 Element not in the DOM even after document.ready has been loaded 

Suppose a list needs to be shown of all the employees in multinational corporations who have received their arrears for the previous financial years on the website of the company. If an async HTTP call has been used to load the data of employees from the server, then the document will get ready, but the data is yet to be retrieved from the server. If the manipulation is done in the DOM, then it would look something like this, which might not work: 

<script>
$(document).ready(function() {
$("li.employee-list-element").css("color", "red");
});
</script>

Although there is no list that has been loaded in the DOM, hence your HTML would like this: 

<ul>
<li class="employee-list-element" ng-repeat="employee in employees"></li>
</ul>

This aforesaid jQuery code will not provide any result even on the DOM ready. In order to make this arrangement work, the timeout can be used in the jQuery code as shown below: 

<script>
  $(document).ready(function() {
    changeColor();
  });
  function changeColor() {
    if ($('li.employee-list-element').length)
      $("li.employee-list-element").css("color", "red");
    else {
      setTimeout(function() {
        changeColor()
      }, 500);
    }
  }
</script>

However, this is also not a good practice. Instead, a built-in directive of Angular 1.5 can be used as provided below. The use of the latest version of AngularJS shall be covered in the next article.  

<ul>
<li class="employee-list-element" ng-style="{'color' : 'red'}" ng-repeat="employee in employees"></li>
</ul> 

Hence, jQuery can be avoided in such kind of similar scenarios by using built-in directives for DOM manipulation. You can also write your own directive in HTML and JS instead of relying on the jQuery that will not guarantee the results. 

#2 When a jQuery code is executed, then the developer would need to call the $digest() on own. However, there is already an AngularJS solution crafted for AngularJS that can work comparatively better.

MISTAKE-2: Not Using the Available Tools

There are a lot of tools that are already provided by the AngularJS, but developers don’t make the best use of them. It is disappointing because sooner or later, these tools have to be put into the use while working on the technology. For example, Google Chrome establishes uncommon improvement strings that include features like investigation and profiling. These tools can help a developer.

MISTAKE-3: Not Using Batarang 

Batarang is a very useful and efficient extension of Google Chrome that can be used for debugging as well as developing AngularJS applications. Batarang can help a lot when working on abstracting scopes, where the arguments are limited. Not using a Batarang is a common mistake, and using this tool to its full potential can be useful. 

MISTAKE-4: Not clearing up - Timeouts, watchers, variables, and intervals

AngularJS on your behalf can perform various functions, but the following things are required to be cleaned up manually.

  • Watchers that are not bound to the existing scope.
  • Timeouts
  • Intervals
  • DOM directives referenced by variables
  • Unreliable jQuery plugins

If these are not cleaned up manually, then programmers may face memory leaks and unexpected behavior. Even worse can happen instantly, it may not be visible, but eventually, it will start appearing. 

Amazingly, AngularJS has a handful of ways to deal with all these issues:

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

  };
 

Observe the jQuery “ $destroy “ event. It is similar to AngularJS but is actually handled separately. The scope watchers don’t react with the jQuery event.  

MISTAKE-5: Using Too Many Watchers

This is quite simple to understand. Here, you need to understand about $digest(). AngularJS creates a watcher for each of the binding {{ model }}. In the digest phase, the bindings are evaluated and compared with their previous value. $digest performs the dirty checking, and if it observes any change in the value, it will fire the watcher callback. If the watcher callback model is modified for an exceptional case, a new digest cycle up to the value of 10 maximum is fired. 

If the expressions are not complex, then the browsers will not face any issues even in creating hundreds of bindings. If you ask how many watchers we can have generally, then the answer is 2000.      

So, how to limit the watchers’ number? 

When you don’t expect to change the scope models, then stop applying watchers there. It’s quite easy in AngularJS 1.3. In the core itself, one-time binding is done. 

<li ng-repeat="item in ::vastArray">{{ ::item.velocity }}</li>

Once the “vastArray” and item.velocity are evaluated, these will never change at all. To get specific results, you can add filters to the array because the whole array is not required to be evaluated to get the particular output.

MISTAKE-6: Ignoring the Source Code on the NG-INIT Example

Ng-init might sound similar to ng-if and ng-repeat, but that’s not the case. There is always a comment in the docs that should be used. Normally we would expect the directive to start a model, and that’s what it does, but it is applied in a different way and does not watch the attribute value. Let’s have a look at the AngularJS source code: 

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

The above code is quite readable except for the directive syntax. The sixth line in the code scope.$eval(attrs.ngInit); is what it is all about. 

Let’s compare it to the ng-show below:

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

Now in the sixth line of the code above, you will see there is a $watch that makes this directive dynamic. A big part of the codes in the AngularJS source code are comments that explain the codes that are readable from the very beginning. 

MISTAKE-7: Overusing and Misunderstanding the Resolves

It is because of ‘resolves’ that add additional time to the loading of the view. However, the primary goal should be the high-performance of the front-end application. There should be no problems to provide some parts of the view when the application waits for the data from the API. Instead of blocking the whole screen, the developers can show the sections that are not dependent upon API data, and this way, the user will not have to wait for the data that is already available. You can consider this following setup: 

function resolve(index, timeout) {
  return {
    data: function($q, $timeout) {
      var deferred = $q.defer();
      $timeout(function () {
        deferred.resolve(console.log('Data resolve called ' + index));
      }, timeout);
      return deferred.promise;
    }
  };
}

function configResolves($stateProvide) {
  $stateProvider
    // MAIN ABSTRACT STATE, ALWAYS ON
    .state('main', {
      url: '/',
      controller: 'MainController as MC',
      templateUrl: '/routing-demo/main.html',
      resolve: resolve(1, 1597)
    })
    // A COMPLEX PRODUCT PAGE
    .state('main.product', {
      url: ':id', 
      controller: 'ProductController as PC',
      templateUrl: '/routing-demo/product.html',
      resolve: resolve(2, 2584)
    })
    // PRODUCT DEFAULT SUBSTATE
    .state('main.product.index', {
      url: '',
      views: {
        'intro': {
          controller: 'IntroController as PIC',
          templateUrl: '/routing-demo/intro.html'
        },
        'content': {
          controller: 'ContentController as PCC',
          templateUrl: '/routing-demo/content.html'
        }
      },
      resolve: resolve(3, 987)
    });
}

The console output would look something like this:

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

This output means that: 

  • The resolves have been executed asynchronously.
  • There cannot be any dependency on the order of execution.
  • All the states will remain blocked unless the resolve completes their thing. 

Before a user sees any output, it is necessary to wait for all the dependencies.  

MISTAKE-8: Misunderstanding the Digest 

In AngularJS, DOM is updated when the watcher callback function is fired. Every binding in the directive is {{ somemodel }} is set with the watchers, but watchers can be set for other directives like ng-repeat and ng-if. It’s readable in the source code, and also manually, you can set the watchers. 

Scopes are bound to watchers. $Watchers take the strings specifically that are assessed against the scope, which are bound to $watch(). These can evaluate the functions and take callbacks. So, when you call $rootScope, all the $scope variables called registered models are checked and compared with the previous values. If the values are different, $watch() callback is executed. 

It’s important to note that even the values are changed in the model’s value, but the callback will be fired in the upcoming digest phase only. It’s called phase because it includes various digest cycles. If the watcher changes, the scope model initiates the execution of a digest cycle. 

Core directives, methods, services, etc., call the $digest(). If through a custom function, you change the model which doesn't call .$applyAsync, .$apply, .$evalAsync, or anything similar which ultimately calls $digest() and the bindings are not updated. The $digest source code is so complex and difficult to understand.

MISTAKE-9: Declaring Everything Using Anonymous Functions

You may have noticed earlier anonymous functions were passed for internal declarations. But later on, developers started defining the function first and used to pass it. This style guide you can get from Todd Mottoes and Airbnb’s. It has lots of advantages and almost no limitations. 

Firstly, mutate or manipulate the objects and functions while assigning variables to it. Secondly, code becomes simple to understand and can be split into files; thus, maintainability becomes easier. Without polluting the global space, you can wrap each particular file into IIFEs. It also enhances testability. For example:

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

We can substitute the publicMegthod1, but it’s already exposed? Wouldn’t it be easier to spy over the existing method? Meanwhile, the method is another function. Have a look at this another form of approach:

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

It’s not all about the style only; it also impacts the code reusability. The code becomes more expressive and gets distributed in self-sustained blocks.

MISTAKE-10: Not Performing Unit Test

The test doesn’t make your code error-free in AngularJS, but it assures you that your team doesn't fall into issues at the time of a regression test. A unit test is worthy not only because it’s more important than an end to end test but also because of its fast execution. 

Test-driven Development is a good practice as implemented, for example, a gulp-karma runner, through which you can run unit testing in each of the files saved. 

You can perform a test by simply writing down empty assurances initially:

describe('some module', function () {
  it('should call the name-it service...', function () {
    // leave this empty for now
  });
  ...
});

After this, reframe the actual code, for testing fill in the assurances with the concerned test code. 

Perform the TDD in terminal to eventually speed up the whole process by 100%. Unit tests can be performed within a few seconds, even if there is a task in bulk. You just need to save your test file and the runner on its own pick and evaluate tests to provide you with instant feedback. 

End to end tests slow down the process, so split the code into test suites and execute testing one by one. Protractor supports the task testing; check the code given below:

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

Conclusion

AngularJS is a wonderful technology with a very wide scope and potential to build first-class applications. As such, understanding the above-mentioned mistakes can help to improve the daily development practice of AngularJS developers to provide a better product to the clients. Mistakes are bound to happen, and nobody can escape it, but if we learn to overcome them with our updated knowledge, then a difference can be observed for sure. What developers can do before resolving or addressing the errors is to prepare a note to write down the common mistakes and ensure that they are not repeated in the future.

The best AngularJS developers know what are the common development mistakes. And, so, they avoid making such mistakes for a flawless software project. 

Sushmita Sen
Sushmita Sen

Sushmita Sen is a technical content writer at APPWRK IT Solutions, a company that caters to the diverse IT requirements of individuals and enterprises in the United States region. She likes to write about the latest digital trends and possess an in-depth knowledge of topics like web development and app development.

Read Similar Blogs

Five Cost-Effective Benefits of Hiring a Java Development Firm

Five Cost-Effective Benefits of Hiring a Java Development Firm

Java is the most popular programming language in the world, thanks to its platform independence, utmost ease of use, and cross-platform compatibility. Being a h ... Read more

50 Essential Terms Associated with PHP

50 Essential Terms Associated with PHP

“Clean code always looks like it was written by someone who cares.”― Robert C. Martin (editor-in-chief of C++ Report magazine) When it come ... Read more

10 Key Questions To Ask a Python Development Agency Before You Hire Them

10 Key Questions To Ask a Python Development Agency Before You Hire Them

Software applications have an immense influence on our lives, and it is continuously empowering us to do things effortlessly. From booking an air ticket to stre ... Read more