Smart tips for navigating AngularJS

A 6 minute read written by Carlos October 10, 2015

Smart tips for navigating AngularJS

I consider myself a JavaScript novice at best: good enough to take advantage of Ajax requests or build a nice-looking modal. I hadn’t kept myself terribly informed on AngularJS, and was a bit blindsided at its complexity, and at times, unconventional implementations.

I know there are quite a few people in a similar situation, so I’ve been collecting solutions to some of the quirks I’ve run into, or some of the less obvious concepts in Angular. This is not meant to substitute the official AngularJS documentation in any way — only to supplement and ease the learning curve!

Scope

Arguably, the linchpin of Angular’s functionality is its handling of scope. I found the documentation explained it well conceptually, but ran into a few nuances that required a decent amount of Google-fu before I understood some of the implementation. I also had some trouble initially understanding when to use one scope configuration over the other. Below are some "in practice" descriptions of the scope configurations I’ve used, the sorts of situations they have so far felt appropriate for, and some of the nuances I mentioned that go along with them.

One-way scope binding

One-way scope binding (set with "@", as shown in the example later) is used to get values for a directive or controller that the sending code does not need to maintain a reference to. I’ve found this to be useful for sending various execution and state flags or booleans, but it can be used anytime the caller does not need to know about potential changes to the value of the passed parameter.

An important "gotcha" here is that one-way scope binding sends the evaluated value of the attribute, and as such, it only sends strings. Consider the following example:

In the caller directive:



$scope.context = "entity panel";
$scope.user_id = 48;
$scope.entity = {
  id: 483,
  type: "feed",
  getFeedData: function() { … }
};

In the caller template:


<render-feed context="{{ context }}" uid="{{ user_id }}"></render-feed>
// $scope.context= "entity panel"
// $scope.uid = "48"

And the render-feed directive’s scope definition:


scope:{
  context: "@",
  uid: "@",
  entity: "@"
}

Using {{ }} to "evaluate as", writing the string and integer parameters works as we’d expect (the integer is interpreted as a string here due to JavaScript being weakly typed). How would we send an object though? Trying to send it in the same fashion as the other variables looks like this:


<render-feed context="{{ context }}" uid="{{ user_id }}" entity="{{ entity }}"></render-feed>
// $scope.context= "entity panel"
// $scope.uid = "48"
// $scope.entity = "{"id":483,"type":"feed","getFeedData":function(){…}}"

The result is the object being evaluated as a string. While it is possible to use JSON.parse() to convert the string, it feels like an unintended, forced solution for a very common requirement.

Bi-directional scope binding

In order to send the object as JSON as we originally intended, we need to use bi-directional scope binding, represented by an equals sign "=". As the name implies, it is a two-way scope used when you want the calling directive/controller to maintain a reference to the variable value being sent. This is useful for giving a called directive access to specific objects that are shared with the parent (and possibly other ascending scopes). Think "pass by reference" when using two-way scope access.

Going back to our example, passing by reference isn’t actually what we’re interested in. Being able to send complex objects and JSON between directives is in essence married to the caller maintaining that variable reference. In my experience, this has for the most part been useful, but before I knew that the two concepts went hand in hand, it caused some confusion and unexpected behaviour.

To continue the example, let’s change the render-feed directive’s scope to the following:


scope:{
  context: "@",
  uid: "@",
  entity: "="
}

Lastly, and very importantly, you cannot use the evaluate statement "{{ variable }}" with two-way scope attributes — it will result in a horrible and obtuse error! Instead, simply write the scope variable name as a string, and Angular takes care of the rest behind the scenes:


<render-feed context="{{ context }}" uid="{{ user_id }}" entity="entity" ></render-feed>
// $scope.context= "entity panel"
// $scope.uid = "48"
// $scope.entity = Entity { uid: 483, type: "feed", getFeedData: function getFeedData() }

A final note on scopes: omitting a scope configuration entirely actually has its own distinct behaviour, and it may not be what you’d expect. Having no scope configuration set results in the scope being fully shared and accessible between the parent and child. I was initially under the impression that no scope configuration would result in the opposite: nothing being shared. To do this, use "scope: false" instead.

Asynchronous calls and waiting for stuff

Angular uses all manner of asynchronous code execution. So far, I have used this mostly making REST calls, and in practice functions like a multi-threaded program. The benefits here are countless, but it also creates many scenarios where some extra safety checks are required.


service.loadAllTheThings({}, function(all_the_things){
  all_the_things.some_number * 5;
};

Depending on just how quickly your service can load the data (hint: not quickly enough), the above code may or may not error on "cannot call property ‘some_number’ of undefined". Angular’s $promise is great for scenarios like this, and is as easy as an additional wrapping function:


service.loadAllTheThings({}, function(all_the_things){
  all_the_things.$promise.then(function(){
    all_the_things.some_number * 5;
  });
};

This is almost ideal, but I found that a variation of the same problem would occasionally occur: "cannot call property ‘$promise’ of undefined". Unable to rely solely on the promise objects being returned in time, I created a final "waitFor" wrapper that, when used in conjunction with the promises, has (so far) weathered all asynchronous errors regarding only executing code when it is safe to do so.


$scope.waitFor = function(data, callback) {
  (data) ? callback() : $timeout(function(){$scope.waitFor(callback);}, 20);
}; 

service.loadAllTheThings({}, function(all_the_things){
  $scope.waitFor(all_the_things, function() {
    all_the_things.$promise.then(function(){
      all_the_things.some_number * 5;
    });
  });
};

While it is possible to use Angular’s $watch functionality and achieve the same end result, I found this to be a simpler, more direct approach for a more specific (but common) requirement. $watch felt better suited for setting listeners on changes to DOM attributes such as user input, or waiting waiting until DOM activity is finished (page transitions, rendering, animations, etc.). The above implementation does not use listeners and is intended as a one-time wait - it addresses the problem of load latency specifically.

Note: A simple null check will not achieve the desired results in these scenarios, as your condition will only evaluate once, instead of retrying until the data exists.

Note again: It is important to note that the usage of Angular’s $timeout as opposed to setTimeout is deliberate — setTimeout does not run $scope.apply() on completion, which can cause problems elsewhere.

Simulating "Pass by value"

As someone versed in lower-level languages, I occasionally find myself at odds with how JavaScript handles variable references (a variable’s type determines how it is passed). Primitive values are always passed by value, while objects are always passed by reference.


var increment = function(n) {
  (n.p) ? n.p++ : n++;
};

var primitive = 5;
var obj = {p:5};

increment(primitive);
increment(obj);

console.log(primitive, obj);  // 5, 6

In lieu of a native operator for changing this behaviour, the generally accepted method of passing complex variables by value is to create a "deep clone".

An important note is that Angular’s scope object always passes and assigns by reference. This is, at least in part, how its continuous data binding functions. Sometimes there are cases where I don’t want changes made to a data set to necessarily reflect in the view (cases like the previous example still occur too). A common scenario I’ve found is maintaining a user editable object’s original and edited state.


<label> Phone Number:
  <input ng-model="user.contact_info.phone" >
</label>
<label> Email:
  <input ng-model="user.contact_info.email" >
</label>

The above is a simple example of a part of a form for updating a user’s contact info. A copy of the original values of user.contact_info are required in the event a user makes edits, and then hits cancel.


$scope.usercopy = $scope.user;
console.log($scope.usercopy.contact_info.phone); // "123-456-7890"
console.log($scope.user.contact_info.phone); // "123-456-7890"
/* user updates input field to "555-658-7930" */
console.log($scope.usercopy.contact_info.phone); // "555-658-7930"
console.log($scope.user.contact_info.phone); // "555-658-7930" :-(

If we instead try using angular.copy(), it works just fine:


$scope.usercopy = angular.copy($scope.user);

console.log($scope.user.contact_info.phone); // "123-456-7890"
console.log($scope.usercopy.contact_info.phone); // "123-456-7890"
/* user updates input field to "555-658-7930" */
console.log($scope.user.contact_info.phone); // "555-658-7930" :-)
console.log($scope.usercopy.contact_info.phone); // "123-456-7890"

Hopefully these tips can help you navigate and understand some of the basics of AngularJS more clearly, and help facilitate some common development tasks. I’ve only covered a few of the many interesting parts of how AngularJS works, so feel free to add some tips of your own in the comments below!