Angular JS Book: Chapter 7: Otros aspectos

[Fuente: AngularJS book by Brad Green and Shyam Seshadri]

In this chapter, we will take a look at some other useful features that are present in AngularJS, but weren’t covered at all or in depth in the chapters and examples so far.

$location

Up to now, you have seen quite a few examples of the $location service in AngularJS.Most of them would have been fleeting glances—an access here, set there. In this section,we will dive into what exactly the $location service in AngularJS is for, and when you should and shouldn’t use it.

The $location service is a wrapper around the window.location that is present in any browser. So why would you want to use it instead of working directly with window.location?

  • Goodbye global state : window.location is a prime example of global state (actually, both window and document objects in the browser are prime examples). The minute you have global state in your application, testing, maintaining and working with it becomes a hassle (if not now, then definitely in the long run). The $location service hides this nastiness (what we call global state), and allows you to test the browser’s location details by injecting mocks during your unit tests.
  • API: window.location gives you total access to the contents of the browser location.That is, window.location gives you the string while $location gives you nice,jQuery-like setters and getters to work with it in a clean way.
  • AngularJS integration:If you use $location, you can use it however you want. But with window.location, you would have to be responsible for notifying AngularJS of changes, and listen to changes as well.
  • HTML5 integration:The $location service is smart enough to realize when HTML5 APIs are available within a browser and use them. If they’re not available, it will fall back to the default usage.

So when should you use the $location service? Any time you want to react to a change in the URL (that is not covered by the $routes, which you should primarily use for working with URL-based views), as well as effect a change in the current URL in the browser.

Let’s consider a small example of how you would use the $location service in a realworld application. Consider a case where we have a datepicker, and when a date is selected, the app navigates to a certain URL. Let us take a look at how that might look:

// Assume that the datepicker calls $scope.dateSelected with the date
$scope.dateSelected = function(dateTxt) {
  $location.path('/filteredResults?startDate=' + dateTxt);
  // If this were being done in the callback for
  // an external library, like jQuery, then we would have to
  $scope.$apply();
};

——

*NOTA: To $apply, or Not to $apply?

There is confusion amongst AngularJS developers about when $scope.$apply() should be called and when it shouldn’t.Recommendations and rumors on the Internet are rampant. This section will make it crystal clear.

But first, let us try to put $apply in a simpler form.

Scope.$apply is like a lazy worker. It is told to do a lot of work, and it is responsible for making sure that the bindings are updated and the view reflects all those changes. But rather than doing this work all the time, it does it only when it feels it has enough work to be done. In all other cases, it just nods, and notes the work for later. It only actually does the work when you get its attention and tell it explicitly to work. AngularJS does this internally at regular intervals within its lifecycle, but if the call comes from outside (say a jQuery UI event), scope.$apply just takes note, but does nothing. That is why we have to call scope.$apply to tell it, “Hey! You need to do this right now, and not wait!”

Here are four quick tips about when (and how) to call $apply.

  • DO NOT call it all the time. Calling $apply when AngularJS is happily ticking away (in its $digest cycle, as we call it) will cause an exception. So “better safe than sorry” is not the approach you want to use.
  • DO CALL it when controls outside of AngularJS (DOM events, external callbacks such as jQuery UI controls, and so on) are calling AngularJS functions. At that point, you want to tell AngularJS to update itself (the models, the views, and so on), and $apply does just that.
  • Whenever possible, execute your code or function by passing it to $apply, rather than executing the function and then calling $apply(). For example, execute the following code:
$scope.$apply(function() {
  $scope.variable1 = 'some value';
  executeSomeAction();
});
instead of the following:
$scope.variable1 = 'some value';
executeSomeAction();
$scope.$apply();

While both of these will have the same effect, they differ in one significant way.The first will capture any errors that happen when executeSomeAction is called, while the latter will quietly ignore any such errors. You will get error notifications from AngularJS only when you do the first.

  • Consider using something like safeApply:
$scope.safeApply = function(fn) {
  var phase = this.$root.$$phase;
  if(phase == '$apply' || phase == '$digest') {
    if(fn && (typeof(fn) === 'function')) {
      fn();
    }
  } else {
    this.$apply(fn);
  }
};

You can monkey patch this into the topmost scope or the rootscope, and then use the $scope.$safeApply function everywhere. This has been under discussion, and hopefully in a future release, this will be the default behavior.

—-

What are those other methods also available on the $location object? Table 7-1 contains a quick summary for you to use in a bind.

Let us take a look at how the $location service would behave, if the URL in the browser was

http://www.host.com/base/index.html#!/path?param1=value1#hashValue.

Table 7-1. Functions on the $location service
Getter Function –> Getter Value –> Setter Function

  • absUrl() –> http://www.host.com/base/index.html#!/path?param1=value1#hashValue  –> N/A
  • hash() –> hashValue –> hash(‘newHash’)
  • host() –> www.host.com –> N/A
  • path() –> /path –> path(‘/newPath’)
  • protocol() –> http –> N/A
  • search()  –> {‘param1’: ‘value1‘} –> search({‘c’: ‘def’})
  • url() —> /path?param1=value1?hashValue –> url(‘/newPath?p2=v2’)

The Setter Function column in Table 7-1 has some sample values that denote the type of object the setter function expects.

Note that the search() setter has a few modes of operation:

  • Calling search(searchObj) with an object<string, string> basically denotes all the parameters and their values
  • Calling search(string) will set the URL params as q=String directly in the URL
  • Calling search(param, value) with a string and value sets (or calling with null removes) a particular search parameter in the URL Using any one of the setters does not mean that window.location will get changed instantly!

The $location service plays well with the Angular lifecycle, so all changes to the location will accumulate and get applied together at the end of the cycle. So feel free to make those changes, one after the other, without fear that the user will see a URL that keeps flickering and changing underneath him.

HTML5 Mode and Hashbang Mode

The $location service can be configured using the $locationProvider (which can be injected, just like everything else in AngularJS). Of particular interest are two properties on this provider, which are:

  • html5ModeA boolean value which dictates whether the $location service works in HTML5 mode or not
  • hashPrefixA string value (actually a single character) that is used as the prefix for Hashbang URLs (in Hashbang mode or legacy browsers in HTML5 mode). By default it is empty, so Angular’s hash is just ‘’. If the hashPrefix is set to ‘!’, then Angular uses what we call Hashbang URLs (! followed by the url).

You might ask, just what are these modes? Well, pretend that you have this super awesome website at www.superawesomewebsite.com that uses AngularJS.

Let’s say you have a particular route (with some parameters and a hash), such as /foo?bar=123#baz.

In normal Hashbang mode (with the hashPrefix set to ‘!’), or in legacy browsers without HTML5 mode support, your URL would look something like:

http://www.superawesomewebsite.com/#!/foo?bar=123#baz

While in HTML5 mode, the URL would simply look like this:

http://www.superawesomewebsite.com/foo?bar=123#baz

In both cases, location.path() would be /foo, location.search() would be bar=123, and location.hash() would be baz. So if that is the case, why wouldn’t you want to use the HTML5 mode?

The Hashbang approach works seamlessly across all browsers, and requires the least amount of configuration. You just need to set the hashBang prefix (! by default) and you are good to go.

The HTML5 mode, on the other hand, talks to the browser URL through the use of HTML5 History API. The $location service is smart enough to figure out whether HTML5 mode is supported or not, and fall back to the Hashbang approach if necessary, so you don’t need to worry about additional work. But you do have to take care of the following:

Server-side configuration
Because HTML5 links look like any other URL on your application, you need to take care on the server side to route all links within your app to your main HTML (most likely, the index.html). For example, if your app is the landing page for superawesomewebsite.com, and you have a route /amazing?who=me in your app, then the URL that the browser would show is

http://www.superawesomewebsite.com/amazing?who=me+.

When you browse through your app, this will be fine, as the HTML5 History API kicks in and takes care of things. But if you try to browse directly to this URL, your server will look at you as if you have gone crazy, as there is no such known resource on its side. So you would have to ensure that all requests to /amazing get redirected to /index.html#!/amazing.

AngularJS will kick in from that point onward and take care of things. It will detect changes to the path and redirect to the correct AngularJS routes that were defined.

Link rewriting
You can easily specify URLs as follows:

<a href="/some?foo=bar">link</a>

Depending on whether you are using HTML5 mode or not, AngularJS will take care to redirect to /some?foo=bar or index.html#!/some?foo=bar, respectively. No additional steps are required on your part. Awesome, isn’t it?

But the following types of links will not be rewritten, and the browser will perform a full reload on the page:

a.Links that contain a target element such as the following:

<a href="/some/link" target="_self">link</a>

b. Absolute links going to a different domain:

<a href="http://www.angularjs.org">link</a>

This is different because it is an absolute URL, while the previous example used the existing base URL.

c. Links starting with a different base path when one is already defined:

<a href="/some-other-base/link">link</a>

Relative Links
Be sure to check all relative links, images, scripts, and so on. You must either specify the URL base in the head of your main HTML file (<base href=”/my-base”>), or you must use absolute URLs (starting with /) everywhere because relative URLs will be resolved to absolute URLs using the initial absolute URL of the document, which is often different from the root of the application.

Running Angular apps with the History API enabled from document root is strongly encouraged, as it takes care of all relative link issues.

AngularJS Module Methods

The AngularJS Module is responsible for defining how your application is bootstrapped.It also declaratively defines the pieces of your application. Let us take a look at how itaccomplishes this.

Where’s the Main Method?

Those of you coming from a programming language like Java or even Python might be wondering, where is that main method in AngularJS? You know, the one that bootstraps everything, and is the first thing to get executed? The one that functions in JavaScript and instantiates and wires everything together, then tells your application to go run?

AngularJS doesn’t have that. What it has instead is the concept of modules. Modules allow us to declaratively specify our application’s dependencies, and how the wiring and bootstrapping happens. The reason for this kind of approach is manifold:

  1. It is declarative. That means it is written in a way that is easier to write and understand.It reads like English!
  2. It is modular. It forces you to think about how you define your components and dependencies, and makes them explicit.
  3. It allows for easy testing. In your unit tests, you can selectively pull in modules, and avoid the untestable portions of your code. And in your scenario tests, you can load additional modules, which can make working with some components easier.

Let us first take a look at how you would use a module that you have defined, then take a look at how we would declare one.

Say we have a module, in fact, the module for our application, called “MyAwesomeApp.”In my HTML, I could just add the following to the <html> tag (or technically, any other tag):

<html ng-app="MyAwesomeApp">

The ng-app directive tells AngularJS to bootstrap your application using the MyAwesomeApp module.

So how would that module be defined? Well, for one, we recommend that you have separate modules for your services, directives, and filters. Your main module could then just declare the other modules as a dependency (just like we did in Chapter 4 with the RequireJS example).

This makes it easier to manage your modules, as they are nice complete chunks of code.Each module has one and only one responsibility. This also allows your tests to load only the modules they care about, and thus reduces the amount of initialization that needs to happen. The tests can be small and focused.

Loading and Dependencies

Module loading happens in two distinct phases, and the functions reflect them. These are the Config and the Run blocks (or phases):

The Config block
AngularJS hooks up and registers all the providers in this phase. Because of this, only providers and constants can be injected into Config blocks. Services that may or may not have been initialized cannot be injected.

The Run block
Run blocks are used to kickstart your application, and start executing after the injector is finished creating. To prevent further system configuration from happening from this point onwards, only instances and constants can be injected into Run blocks. The Run block is the closest you are going to find to a main method in AngularJS.

Convenience Methods

What can you do with a module? We can instantiate controllers, directives, filters, and services, but the module class allows you to do more, as you can see in Table 7-2:
Table 7-2. Module configuration methods
API Method –> Description

  • config(configFn) –> Use this method to register work that needs to be done when the module is loading.
  • constant(name, object) This happens first, so you can declare all your constants app-wide, and have them available at all configuration (the first method in this list) and instance methods (all methods from here on, like controller, service, and so on).
  • controller(name, constructor) We have seen a lot of examples of this; it basically sets up a controller for use.
  • directive(name, directiveFactory) As discussed in Chapter 6, this allows you to create directives within your app.
  • filter(name, filterFactory) Allows you to create named AngularJS filters, as discussed in previous chapters.
  • run(initializationFn) Use this method when you want to perform work that needs to happen once the injector is set up, right before your application is available to the user.
  • value(name, object) Allows values to be injected across the application.
  • service(name, serviceFactory) Covered in the next section.
  • factory(name, factoryFn) Covered in the next section.
  • provider(name, providerFn) Covered in the next section.

You might realize that we are missing the details of three particular API calls—Factory,Provider, and Service—from the preceding table. There is a reason for that: it is quite easy to confuse the usage between the three, so we will dive into a small example that should better illustrate when (and how!) to use each one.

The Factory
The Factory API call is used whenever we have a class or object that needs some amount of logic or parameters before it can be initialized. A Factory is a function that is responsible for creating a certain value (or object). Let us take the example of a greeter function that needs to be initialized with its salutation:

function Greeter(salutation) {
	this.greet = function(name) {
		return salutation + ' ' + name;
	};
}

The greeter factory would look something like:

myApp.factory('greeter', function(salut) {
  return new Greeter(salut);
});

and it would be called as:

var myGreeter = greeter('Halo');

Another simpler example:

var app = angular.module('myApp', []);

// Factory
app.factory('testFactory', function(){
    return {
        sayHello: function(text){
            return "Factory says \"Hello " + text + "\"";
        }  
    }               
});

// AngularJS Controller that uses the factory
function HelloCtrl($scope, testFactory)
{
    $scope.fromFactory = testFactory.sayHello("World");
}

The Service
What about services? Well, the difference between a Factory and a Service is that the Factory invokes the function passed to it and returns a result. The Service invokes “new” on the constructor method passed to it and returns the result.

So the preceding greeter Factory could be replaced with a greeter Service as follows:

myApp.service('greeter', Greeter);

Every time I asked for a greeter, AngularJS would call the new Greeter() and return that.

Another simpler example:

var app = angular.module('myApp', []);

// Service definition
app.service('testService', function(){
    this.sayHello= function(text){
        return "Service says \"Hello " + text + "\"";
    };        
});

// AngularJS Controller that uses the service
function HelloCtrl($scope, testService)
{
    $scope.fromService = testService.sayHello("World");
}

The Provider
This is the most complicated (and thus most configurable, obviously) of the lot.The Provider combines both the Factory and the Service, and also throws in the benefit of being able to configure the Provider function before the injection system is fully in place (in the config blocks, that is).

Let’s see how a modified greeter Service using the Provider might look:

myApp.provider('greeter', function() {
  var salutation = 'Hello';
  this.setSalutation = function(s) {
    salutation = s;
  }
  function Greeter(a) {
    this.greet = function() {
      return salutation + ' ' + a;
    }
  }
  this.$get = function(a) {
    return new Greeter(a);
  };
});

This allows us to set the salutation at runtime (for example, based on the language of the user).

var myApp = angular.module(myApp, []).config(function(greeterProvider) {
  greeterProvider.setSalutation('Namaste');
});

AngularJS would internally call $get whenever someone asked for an instance of the greeter object.
* Warning!
There is a slight, but significant difference between using:
angular.module(‘MyApp’, […])
and:
angular.module(‘MyApp’)

The difference is that the first creates a new Angular module, pulling in the module dependencies listed in the square brackets ([…]). The second uses the existing module that has already been defined by the first call.

So you should make sure that you use the following code only once in your entire application:
angular.module(‘MyApp’, […]) // Or MyModule, if you are modularizing your app

If you do not plan to save it to a variable and refer to it across your application, then use angular.module(MyApp) in the rest of the files to ensure you get a handle to the correct AngularJS module. Everything on the module must be defined by accessing the variable, or be added on the spot where the module has been defined.

Communicating Between Scopes with $on,$emit, and $broadcast

AngularJS scopes have a very hierarchical and nested structure. There is one main $rootScope (per Angular app or ng-app, that is), which all other scopes either inherit, or are nested under. Quite often, you will find that scopes don’t share variables or do not prototypically inherit from one another.

In such a case, how do you communicate between scopes? One option is creating a service that is a singleton in the scope of the app, and processing all inter-scope communication through that service.

There is another option in AngularJS: communicating through events on the scope.There are some restrictions; for example, you cannot generally broadcast an event to all watching scopes. You have to be selective in whether you are communicating to your parents or to your children.

But before we discuss that, how do you listen to these events? Here is an example where our scope on any Star System is waiting and watching for an event we call “planetDestroyed.”

scope.$on('planetDestroyed', function(event, galaxy, planet) {
  // Custom event, so what planet was destroyed
  scope.alertNearbyPlanets(galaxy, planet);
});

Where do these additional arguments to the event listener come from? Let’s take a look at how an individual planet could communicate with its parent Star System.

scope.$emit('planetDestroyed’, scope.myGalaxy, scope.myPlanet);

The additional arguments to $emit are passed on as function parameters to the listener functions. Also, $emit communicates only upwards from its current scope, so the poor denizens of the planet (if they had a scope to themselves) would not be notified if their planet was being destroyed.

Similarly, if a Galaxy wanted to communicate downwards to its child, the Star System scope, then it could communicate as follows:

scope.$emit('selfDestructSystem', targetSystem);

Then all Star Systems listening for the event could look at the target system, and decide if they should self-destruct, using these commands:

scope.$on('selfDestructSystem', function(event, targetSystem) {
  if (scope.mySystem === targetSystem) {
    scope.selfDestruct(); // Go Ka-boom!!
  }
});

Of course, as the event propagates all the way up (or down), it might become necessary at a certain level or scope to say, “Enough! You shall not PASS!” or to prevent what the event does by default. The event object passed to the listener has functions to handle all of the above, and more, so let us take a quick look at what you can get up to with the event object in Table 7-3.

Table 7-3. Event object properties and methods
Property of event –> Purpose

  • event.targetScope –> The scope which emitted or broadcasted the event originally
  • event.currentScope –> The scope currently handling the event
  • event.name –> The name of the event
  • event.stopPropagation() –> A function which will prevent further event propagation (this is available only for events that were $emitted 
  • event.preventDefault() –> This actually doesn’t do anything but set defaultPrevented to true. It is up to the implementer of the listeners to check on defaultPrevented before taking action 
  • event.defaultPrevented –> true if preventDefault was called

fd
fd
fd
fd
ff
f