Communication and data sharing patterns

A typical Angular app employs a number of framework artefacts to achieve any functionality. Any Angular app has multiple views, controllers, services, filters, and also directives, all working together in unison. The need for communication between these constructs thus becomes imperative.

This section highlights how communication can happen among these constructs and how data is shared. It tries to address scenarios such as:

  • How to send data from one view to another on page transition
  • How to communicate and or share data:
    • Between controllers
    • Between controllers and services, other controllers, or directives
    • Via inter-directive communication and even inter-service communication

We have used patterns at various times throughout the book. The aim here is to consolidate our learning and provide a quick reference.

Using a URL to share data across pages

If data needs to be shared across pages, it can be done using route URLs. A route definition is something like this:

/builder/workouts/:id

It allows setting the id fragment (on the source page) to a value that is available on the target view controller, and other controllers that are available when the route is loaded using $routeParams.id.

Passing data using a route is simple but the capability is limited to passing only string parameters. This approach seems similar to the process of using query strings when working with standard web applications with multiple pages.

In spite of this limitation, a lot can be achieved by passing data using routes. Data passed in the URL (using $routeParams) can be used by the target page to retrieve more content based on what is passed in the URL.

Look at the route example outlined previously, it is from the Workout Builder. Clicking on a specific workout tile in Workout List takes us to the workout edit page with the workout name embedded in the URL (/builder/workouts/7minworkout). The WorkoutController object then uses $routeParams.id/$route.current.params.id to actually retrieve the selected workout from the server (using the WorkoutService).

Using $scope

Scopes are a ubiquitous mechanism for communication and data sharing. Scopes are used so often that we do not put much thought into how and what we are actually sharing.

To reiterate how scopes work in Angular, look at this:

  • Scopes are created as part of the directive execution, or as part of the application bootstrapping process when an application starts
  • At any given time in the application, one or more scopes are active
  • Scope hierarchy is driven by the HTML element hierarchy
  • A child scope inherits from its parent scope (the prototypal inheritance) and hence can access all properties of the parent scope (isolated scope is an exception).

Given how scopes work, parent/child views can implicitly communicate with each other using the associated parent scope.

The Workout Runner app is an excellent example of this. It has a workout videos view and a description panel that derive their content from the parent scope.

Scopes as a data sharing mechanism have their limitation too.

The very fact that scopes share data with a child scope through inheritance implies two unrelated scopes or sibling scopes cannot share data/communicate directly. In such a scenario, we need one of the following:

  • Use the parent scope as a communication channel. This parent can be an immediate parent in the case of sibling scopes or a parent scope up higher in the hierarchy.
  • Or as a special case, we can use $rootScope to communicate and share data. Since $rootScope is the root for the complete application scope hierarchy, any data set on $rootScope can be accessed throughout all scopes.
  • Use services to communicate between such scopes. This is possible because services by nature are singleton.

While all these methods try to solve the same problem, there are some that are preferable to others. $rootScope should be avoided as this results in the creation of a global state, which is never a good thing.

Prefer services over parent scopes when it comes to sharing data. The disadvantage of the parent scope is that, just by looking at variables defined on the parent scope, one cannot determine who is consuming them, whereas services makes this dependency explicit.

This also happens when we break a large view into multiple smaller manageable views. We may be taking dependency implicitly on the scope that these new views inherit. In such a case, too, we should try to evaluate whether a service can be used.

Using $rootScope

Since there is a single $rootscope service created for any Angular application, $rootScope can be used to share data. Anything added to $rootScope is accessible throughout the app.

Using $rootScope just to share data among controllers should be avoided. In fact, any global state can easily be managed using services as they too are singleton in nature.

The only reason $rootScope is useful is that it can be injected into services and can be used to broadcast/emit events at a global level.

Using services

The original intent of services is not to share data. Services allow us to encapsulate data and behavior that is not coupled with the view and can be used across the application. But since services are singleton in nature, they can very well be used to share data among Angular constructs.

Create a service and inject it into a controller, directive, or other service itself and you have a working communication channel. When service functions are invoked or data is manipulated, the changes are automatically available to any component that has the service dependency.

When writing services to communicate and share data, write them from a behavioral perspective, without thinking of them as a mere data store (shared).

Inter-directive communication

How can two directives interact? If they are defined on the same HTML tag or follow a parent-child hierarchy, we can use the require attribute of the directive definition object.

To reiterate what you learned in the chapter on directives, use this:

require: ['ngModel'],

To take dependency on another directive (the preceding ngModel) defined on the same element, use this:

require: ['?^busyIndicator'],

This takes dependency on a directive defined on the parent (busyIndicator).

Tip

Remember, directive dependency is about taking dependencies on the directive's controller function.

When require is true, the directive controller dependency is injected into the link function of the dependent directive, as shown here:

link: function (scope, elm, attr, ctrl) {
ctrl.$setValidity(validatorName, data);
}

But if the directives are not defined on the same HTML hierarchy or are not using directive controllers, then they can communicate using either the parent scope, the $rootScope, services, or through events. For directives with isolated scopes, the options are further limited to services, $rootScope, or events. Avoid $rootScope and stick to the service or event broadcast/subscribe in such a case.

Using events

Events are a great mechanism for communicating between components and while keeping them decoupled. A publisher component raises an event, and one or more subscribers can listen to the events and react to them. Events can be used across controllers, directives, filters, or services for communication.

Angular eventing infrastructure is set up using three functions defined on the scope object:

  • To publish $broadcast and $emit: $broadcast sends a message down the scope hierarchy and $emit sends a message up the hierarchy.
  • To subscribe $on.

When using $broadcast or $emit from a scope to raise an event, we need to be aware of where the subscribers are. To reiterate what you learned about eventing in Chapter 3, More AngularJS Goodness for 7 Minute Workout, have a look at this:

  • With $scope.$emit, event messages travel up, hence any upstream parent scope can subscribe to these event messages
  • With $scope.$broadcast, event messages travel down, hence any downstream child scope can subscribe to these messages

But if the publisher and subscribers do not share a parent-child hierarchy, then the publisher can use $rootSope.$broadcast to communicate. $rootScope.$broadcast propagates the event through every available scope in the application.

While $emit and $broadcast are great mechanisms for communicating, we should be careful not to overuse them. Too many events can make an application difficult to understand and debug as the flow of the application is not linear any more.

With this we have covered every communication mechanism/pattern available.

Tip

As far as possible, stick to services to share data and the events to publish important changes that happen during the execution of the app.

Let's focus our attention on another critical area of Angular apps: performance!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.17.40