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:
We have used patterns at various times throughout the book. The aim here is to consolidate our learning and provide a quick reference.
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
).
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:
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:
$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.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.
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.
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).
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
).
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.
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:
$broadcast
and $emit
: $broadcast
sends a message down the scope hierarchy and $emit
sends a message up the hierarchy.$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:
$scope.$emit
, event messages travel up, hence any upstream parent scope can subscribe to these event messages$scope.$broadcast
, event messages travel down, hence any downstream child scope can subscribe to these messagesBut 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.
Let's focus our attention on another critical area of Angular apps: performance!
52.14.17.40