In chapter 3, you learned a great deal about modular programming. One of the biggest takeaways from that chapter is the idea that you can internalize the complexity of your code and provide a public API to its features by applying an architectural design pattern commonly referred to as the module pattern. This is a way to achieve encapsulation in JavaScript.
As you discovered, coding with modules helps organize your application’s logic into small, single-purpose units, which are easier to maintain and update. This inevitably leads to greater reusability for your code. Using modules also helps with data integrity, code organization, and the avoidance of name collisions. After all, you’re creating code in a single, nonrefreshing page. Without this kind of design for your application’s code base, relying purely on global variables and functions would quickly become unmanageable (see figure 6.1).
Even though the modules themselves are at the heart of modular programming, being able to use them to create a successful SPA requires more than knowing how they work mechanically. You also need to understand how they can interact: how one module can invoke the functionality of another module and, possibly, receive a response.
This chapter continues talking about modules but this time within the context of shaping your SPA’s architecture by the way you design module interaction. The chapter begins with a review of a module’s structure at a high level but mainly concentrates on the design of the inter-module interaction process.
Because modules are based on a variety of pattern styles (such as the traditional, revealing, AMD, and AngularJS-styled modules, to name a few), I’ll keep our discussion as neutral as possible when covering the chapter’s concepts. Also, as in the other chapters, I’ll include highlights from a concrete example, with its entire source available for download.
For your project this time, you’ll create an SPA for an acquaintance who wants to start an online store to sell used video games. You won’t need to get into the complexities of having a shopping cart in this exercise. You’ll instead focus on the store’s product search feature. This application, though simple, will still give you a chance to create several modules and design how they’ll interact, without drowning in source code.
As in the preceding chapter, you’ll save the details of the project for later. Even though the interface is fairly trivial, we’ll make sure that the modules that power the application are interesting. Before talking about methods for inter-module interaction, though, let’s set the stage by reviewing some basic concepts for modular programming.
Let’s begin by reviewing some basic module concepts at a high level. We’ll use this as a baseline for the rest of our discussion.
Because the JavaScript specification at the time of this writing has no built-in syntax for creating modules or classes to encapsulate parts of your code, it’s simulated using the module pattern.
The next version of JavaScript, ECMAScript 6 (also called Harmony or ES.next), adds official support of the module to the language.
A module, as far as JavaScript goes, is a specially constructed function. This type of function is often called an immediately invoked function expression (IIFE).
As a reminder, the module pattern’s outer function is often referred to as an immediately invoked function expression, or IIFE, because it’s written as a function expression (it doesn’t start with the function keyword) instead of a function declaration and has a trailing set of parentheses to make the function get invoked immediately. The IIFE’s syntax looks like this:
var x = (function() { // do something })();
If you want to learn more about function expressions and how they differ from function declarations, here’s a good resource: http://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions
The following listing is an example of the traditional module pattern. It creates a module to apply a discount to the price of a product. The price of the product is passed in via the calculate function, and the new, discounted price is returned.
For AMD/CommonJS modules, an assignment to a global variable isn’t needed.
Chapter 3 also introduced a popular variation of the module pattern called the revealing module pattern. The following listing shows the same module written using this style.
In this version, everything is the same except for the returned object literal. Here, the public function is merely a pointer to the internal code. This makes the API cleaner and easier to read.
With either version, the pattern’s design enables the module to be used as a wrapper for a piece of functionality (see figure 6.2).
The outer function of the module pattern forms a kind of protective barrier around your code. This is possible thanks to the limiting scope of the outer function.
Scope (in a broad sense) refers to the accessibility of one part of an application to another part of that application.
The clever design of this function also enables you to avoid polluting the global namespace with your application’s variables and functions, because they’re local to the module’s outer function.
Another nice feature of the module pattern is that it allows for the creation of an application programming interface, or API. An API is like the module’s contract when another module wants to talk to it. The API defines what’s publicly available. Code from other modules can use the API to gain limited and controlled access to its internal code.
The API is formed via the module’s return statement (see figure 6.3). This forms a bridge between the internal functionality of the module and the outside world.
In figure 6.3, we’re returning an object defined with object literal syntax. In the returned object, any object member names to the left of the colon are exposed. Those to the right are the references to the internal code of the module.
After the object is returned, it’s assigned to an external variable. This variable acts like a remote control to the module’s functionality. Other modules will send messages to the object referenced by the variable. This variable will continue to hold a valid reference to that object as long as the variable persists (see figure 6.4).
Providing an API for your encapsulated code enables you to not only access the code within the module but also custom-tailor any interaction with it. You can purposely name the exposed functions of the API something meaningful to the others while naming private functions something meaningful only to the internal code. You can also choose to expose certain things about the module’s inner workings while hiding others.
Remember that you’re not hiding functionality out of secrecy. You’re limiting what’s exposed in the API to only what’s needed for other modules to successfully use it.
When you design a module, you try to limit the scope of its functionality to a single purpose. Having only one purpose per object is the crux of the single-responsibility principle (SRP) for software design. You can extend this idea to the modules you design: the module itself might have many variables and many functions inside it, but all of them are to support the module’s overall reason for existence. When modules are designed with SRP in mind, they become like cogs in a machine. Each cog has a particular purpose but works harmoniously with the other modules of the application.
As the application grows in complexity, it’s also normal for the complexity in your modules to increase. The nice thing about modules, though, is that if their code starts getting out of control in some way (becoming too large or enabling other purposes to emerge, perhaps), you can always refactor and split that module into one or more other modules.
For example, imagine that your video game application begins as a single module, but its functionality eventually grows beyond its initial purpose. The best strategy is to refactor the code, dividing it into smaller, single-purpose modules (see figure 6.5).
Refactoring large, multipurpose modules helps you preserve the SRP aspect of your application’s code base.
Another thing that sometimes emerges when you refactor is the potential to find functionality that can be reused either in the immediate project or in future phases of the project. Reusable components mean less work as your project evolves and grows larger, because having shared modules eliminates the need to repeat code in multiple places.
Currently, your SPA in this chapter’s example displays the price of a game only after a product is selected from the search results. Imagine, however, that your acquaintance calls you back to ask for additional features, such as a shopping cart or a product list, which also require displaying the game’s calculated discount price (see figure 6.6).
With your application designed with reusable modules, you could support these types of enhancement requests with less retooling than typically required.
Being able to design your application’s infrastructure in a modular fashion is well and good, but how are these self-contained units able to interact with each other? In the next section, you’ll look at a couple of basic interaction methods.
Modules interact in two ways: directly via module APIs, which creates a direct dependency, or through events. We’ve talked about module APIs in detail, so this section focuses more on how using dependencies for module interaction affects the application’s architecture. This section also covers the decoupling effect of events and, more specifically, an event aggregation pattern called pub/sub.
Even though the syntax of any of the module pattern styles looks alien, a module is still just a function. As such, you can pass things into it via its parameters. Passing in another module as a parameter is one way modules can interact. This interaction method is considered direct, because one module is directly accessing the API of another. When one module interacts with another by directly calling the other’s API, the other module is known as its dependency.
Each module style provides a way to declare other modules as dependencies. Although the syntax varies, the dependency list of each type serves a universal purpose: to allow a module access to the APIs of other modules so they can interact with one another. If you’re using the traditional module pattern, for example, you declare dependencies in the module’s trailing parentheses and gain access to them via its parameter (dependency) list.
To illustrate, let’s use the pricing module shown previously. To get the price of each selected game at a discount, you need to add it as a dependency to the product display module (see the following listing). You’re also adding the product data module as a dependency to gain access to your stub product data.
After a module is declared as a dependency of another, you can gain access to its API. The module’s API ensures that you access its functionality as it was intended, passing in any necessary information for the call to be successful. Bear in mind that you still don’t have direct access to the dependent module’s module-scoped functions and other objects.
Even though adding a module as a dependency is a direct method of module interaction, you’re still interacting only via the dependent module’s API.
Interacting through dependencies is a good choice for many situations but not always. This method has both pros and cons.
Here are a few advantages and disadvantages of direct inter-module interaction. Don’t see this list as reasons to adopt this method or not, however. It’s not practical to avoid using dependencies in modular programming. Think of the list as a helpful guide for when to use them.
Pros:
Cons:
The other option for module interaction is through events. The next section highlights a popular event aggregation pattern called the publish/subscribe (or pub/sub) pattern. You’ll learn about what publish/subscribe is, how it works at a high level, and some of the pros and cons of using it in your SPA.
Whether you’re talking about interaction with the DOM or interaction between objects as you’ve seen in our discussion of MV* frameworks, events are used extensively in modern applications. You can think of the entire browser environment as being event driven. Events provide a natural way to achieve loose coupling, because recipients can choose to listen or not and also decide on how to respond.
Several design patterns around events have emerged over the years. The one this section focuses on is called the publish/subscribe, or pub/sub, pattern. Pub/sub is a common and useful pattern for interaction between disparate modules. Pub/sub is based on a classic design pattern called the observer pattern.
With the observer pattern, one object is directly observed (the observable), and any number of other objects (called observers) can choose to pay attention to it, as shown in figure 6.7. The observable sends out a notification (typically through events) whenever its state changes so the observers can react accordingly.
What distinguishes pub/sub from the traditional observer pattern is that usually an intermediary service publishes (sends/broadcasts) the notifications on behalf of another object. Other objects in the application can choose to listen or not.
This type of brokered, indirect inter-module interaction is ideal when two unrelated modules need to interact or an application-wide message needs to be broadcast without any expectations by the publisher about what happens when the message is received.
Though not a requirement, notifications with most pub/sub implementations are topic based. A topic (or event name in AngularJS) is a simple name that’s used to represent a particular notification. If another object wants to listen, it subscribes to that topic. When a topical message is published, the message broker delivers that notification to any of the topic’s subscribers.
In the case of your application, you’ve created a module whose sole purpose is to broadcast system-wide messages using AngularJS’s built-in pub/sub mechanism. This module will use pub/sub to publish a message with the topic userMessage.
As you can see in figure 6.8, the topic has only one subscriber: the controller for a view that displays user alerts. Because the controller is a subscriber of the userMessage topic, it will update the text in the view anytime it receives a new message.
Usually, pub/sub topic notifications and subscriptions are created programmatically by using the syntax style provided by the pub/sub implementation. If you decide to use the pub/sub method in your application, you’ll need some type of pub/sub software.
In your SPA, either the message broker implementation will be built into the MV* framework or you’ll have to download one of the many JavaScript pub/sub libraries available. Table 6.1 lists a few of the pub/sub libraries available at the time of this writing.
Pub/sub library |
URL |
---|---|
AmplifyJS | http://amplifyjs.com |
PubSubJS | https://github.com/mroderick/PubSubJS |
Radio.js | http://radio.uxder.com |
Arbiter.js | http://arbiterjs.com |
As with any dependency in your application’s code base, check out all available alternatives, using the usual set of criteria I’ve previously mentioned: learning curve, bugs and fix rate, documentation, maturity, and community support.
The most basic type of notification doesn’t include any data being passed. It’s merely the topic name of the message that’s being published. To illustrate a basic notification in pub/sub, we’ll keep things vendor agnostic by using pseudocode.
We’ll start with how to publish a topical message. To publish a message in module A, you include a line similar to the following:
pSub.publish("hello_world_topic");
It’s that simple. Then, in module B, subscribing to that topic is equally easy. You include the topic name of the message you’re interested in hearing and what you want to happen when you hear it:
pSub.subscribe("hello_world_topic", functionToCallWhenHeard);
You typically use a basic notification to inform all subscribers that something happened. Then each subscriber, upon hearing it, can react in a completely different way.
At times, however, you’ll want to include data along with the topic being published. This, too, is accomplished easily using pub/sub.
In addition to basic notifications, most pub/sub brokers let you pass data along when the message is published. In turn, each subscriber of that topic gets this data passed into its callback function by the broker. To publish with data, you use a line similar to the following in module A:
pSub.publish("hello_world_topic", dataObjectToSend);
In module B, your subscription line would be the same. The message broker passes the data sent into the function you list in the subscription:
pSub.subscribe("hello_world_topic", functionToCallWhenHeard);
The only difference when receiving data is in your callback function itself. Here, you’ll need a parameter in the function’s signature to represent the data being passed to it from the subscription:
function functionToCallWhenHeard( paramForDataPassed ) { ... }
With most brokers, any valid JavaScript object or value can be passed with the notification.
Another feature common to most pub/sub implementations is the ability to unsubscribe. Because subscriptions are topic based, the subscriber can invoke the broker’s unsubscribe function when it doesn’t want to react to that topic anymore. Once again, most pub/sub implementations make doing this super easy:
pSub.unsubscribe("hello_world_topic");
Various other options might be available, such as setting a priority for a topic, but are specialized and vendor specific. Additionally, the options mentioned thus far are the bare minimum but aren’t guaranteed to be available in the pub/sub implementation you’re using. The documentation for the broker you’re using will specify the list of available features.
As noted earlier, pub/sub is a pattern that helps keep the modules of your code base decoupled. It can be a powerful and flexible tool, but it’s not without its disadvantages. The following are some of the main pros and cons of using pub/sub in your SPA.
Pros:
Cons:
Now that we’ve reviewed module concepts and the ways modules can interact, let’s review some of the highlights of this chapter’s project. For consistency with the other chapters in this book, we’ll use AngularJS.
As mentioned at the start of the chapter, in this project you’ll create a simple online store for a friend who has a small business selling used video games. You’ll create only the product search portion of the SPA, because that’s enough to demonstrate both methods of inter-module interaction discussed in this chapter.
In previous chapters, you’ve stuck to a feature or two for your sample application to stay focused on the concepts at hand. But now your application’s code base is a little more elaborate. In this chapter, your application is divided into the following features:
As you go through some of the code highlights, you’ll see the inter-module interaction method used by each module so you can see how the application is connected. Before going over the code, though, let’s discuss the objectives for this project.
For starters, figure 6.9 provides a glimpse of what the application will look like when you’re finished. With that image in mind, here’s a list of features that you want the application to have:
Because previous chapters have thoroughly covered routing and views, I’ll refer to them only when setting the stage for each section of our module discussion. As always, the complete project is available for download.
Because this chapter’s example uses AngularJS, you’ll also need a brief overview of modules and dependencies in AngularJS. Although this discussion of the sample project will be as neutral as possible, you’ll need a little AngularJS knowledge to follow the source code.
In AngularJS, you can create a module by calling the framework’s module() function, supplying it with a name for your module and, optionally, including a dependency list:
angular.module("moduleName", ["dependency1", "dependency2"])
Let’s compare that with the traditional module pattern:
var moduleName = (function(depParam1,depParam2) { })(dependency1, dependency2)
If you don’t have any dependencies, you provide empty brackets:
angular.module("moduleName", [])
After creating your module, you can create Angular-specific components within that module based on what you need your code to do. AngularJS provides the following out-of-the-box components: filters, directives, controllers, values, constants, services, factories, and providers. The details of the various AngularJS components are beyond the scope of this book but can be found in the online documentation at https://angularjs.org.
In addition to the built-in AngularJS directives, you’re using controllers, values, and factories. You’re using the value component to hold your stub data, because this component is ideal for storing values used in an application. The factory components are the closest equivalent to your traditional module pattern (in terms of purpose), so you’ll mostly use those for basic functionality. The controller components will, as you’ve already learned, act as a bridge between your application’s code and the UI.
To create components for a module, you add a component function, such as factory() or controller(), to the module declaration. You can also include the name of other components in your declaration, and AngularJS will inject them into the one you’re creating. This is called dependency injection (or DI).
To tell AngularJS you want another component injected into the component you’re creating, you add the other component to the function parameter list. The names of injected components are duplicated as text strings, to decouple the name of the concrete implementation of the injected dependency from the named reference that the consuming code binds to:
angular.module("moduleName",[]) .factory("componentName", ["otherComponent",function(otherComponent){...}] )
The components you ask AngularJS to inject for you can be any you’ve created in another module (if that module is in the current module’s dependency list) or any one of the out-of-the-box components from AngularJS or other third-party components.
Now that you understand the bare minimum of AngularJS modules, components, and dependencies, you can move on to the source code for this chapter’s SPA example.
When the application loads, users are greeted with a welcome message and a way to search for the games they’re interested in. The header view and the search view are fixed, so they stay present as the main content changes with each search. When a title is searched, clicking the Search button invokes the route to display your search results (see figure 6.10).
Figure 6.11 gives you a big-picture look at this transaction, end to end, and notes the type of inter-module interaction, where relevant.
All searches are keyword searches, so any game with the search term in its title is added to the result list and displayed.
That’s the high-level view of what happens when a user searches. Let’s break down the steps now and look at the code behind each type of module interaction.
The search.controllers module has two controller components: one to handle the search itself and the other to display the search results. It also includes the search.services module as its sole dependency (see figure 6.12).
After searching, your search results controller uses a component in the search.services module to do the work of looking up the term a user has entered. If anything is found, the search component in the search.services module will return a list of game objects for the results controller to pass to its view for display.
The following listing shows the code for your search.controllers module. Remember that in AngularJS you add module-level dependencies in the brackets of the module declaration.
Now that you’ve seen the controllers used for the search, let’s review some of the code in the search.services module that you’ve added as a dependency. This is the module that does the heavy lifting.
The search.services module has two dependencies: one to access your data and another to broadcast the number of search results found (see figure 6.13).
Let’s break up your analysis of the search so it’s easier to read. Let’s start with the dependency list:
angular.module("search.services",["data.appData" ,"messaging.services])
Here you’ll notice that the search.services module also has its own dependencies. Its dependencies include the following:
The following listing provides the entire source code for this module. It’s a lot of code, but most of it is routine JavaScript used to match the search term with any of the game titles.
This asks AngularJS to inject productData from the data.appData module and messageSvc from the messaging.services module. These are the worker bees of these two modules.
For each match of the search term, the information is returned to the caller (the search results controller). The results are then displayed for the user. Figure 6.14 shows the search results view after a successful search. In this case, the user used the term of in the search. This matched the game titles Call of Duty Advanced Warfare and Middle Earth: Shadow of Mordor.
Additionally, the number of records found is briefly presented at the bottom of the screen. Before the searchByTitle function returns, you’ll use the messageSvc component of the messaging.services module to create a message about the number of matches in your search and broadcast it to the rest of the application. This is the message in yellow in figure 6.11. This messaging module will then use pub/sub to do the broadcast.
The messaging module has no module-level dependencies, so no direct interaction occurs with another module. It does, however, interact indirectly with the user alerts module using pub/sub (see figure 6.15).
The code in this module is straightforward. It has a single component called messageSvc that uses the built-in pub/sub system of AngularJS to broadcast any messages passed to it, as you can see in the following listing.
With a generic, system-wide feature such as broadcasting a message, it’s acceptable, if not preferable, to use pub/sub. Also, writing the module in this generic way lets you use it as a general messaging utility that can be reused anywhere you need to broadcast a message.
With the search results being displayed and the number of results being broadcasted, you’re left with the module that will consume the broadcast and display it as a user alert.
The user.alerts module also has no module-level dependencies. As you saw in the previous section, it’s communicated with indirectly by the messaging services module (refer back to figure 6.15).
Inside the module, you have one component that’s a controller, so you can display any information received in the pub/sub subscription in the user alerts view (see the following listing).
At this point, you’ve seen how the application uses both direct and indirect methods of inter-module interaction when searching. Let’s take a look at what happens when the user makes a selection from the search results list.
When the user clicks one of the search results, a change in state allows the application to display the details about the selected game. Figure 6.16 shows an overview of this transaction.
After a game is selected from the search results list, its ID is sent to the product display controller as a parameter.
The productdisplay.services module has only one module-level dependency: the product display services module (see figure 6.17).
As shown in the following listing, you use the productDisplaySvc component of the productdisplay.services module to look up the selected game’s details, using the product ID sent in via the parameter. These details are then assigned to the $scope (ViewModel) for display.
Let’s take a peek at what’s going on inside the product display services module in order to look up the game information and apply the price discount.
The productdisplay.services module has two dependencies: one to access your data and the other to calculate a 40% price discount for the selected used game (see figure 6.18).
You have some typical JavaScript coding in this module to iterate over the list of games and find the one with the matching ID (see the following listing).
Having seen the product display services module, let’s examine the last leg of your transaction to see how the discount is calculated and returned to the calling module.
The pricing.services module has no dependencies of its own and is straightforward. It takes in an amount from an outside module and multiplies that by the discount rate, as shown in the following listing.
After the appropriate game has been found via its ID, and the discount has been applied to its price, a new game object is returned to the controller, where the information is passed over to the view for display. Figure 6.19 shows the outcome of the product selection.
The resulting view is the culmination of your inter-module interaction design for this project, including both the dependency method and the pub/sub method.
Now here’s a challenge for you to see what you’ve learned in this chapter. Create a movie title search for which the matching names of movies appear in a list as the user types into an input text field. Use a single view, with the input text field at the top and an unordered list for the results below it. Use a mix of dependencies and pub/sub to create the functionality. Using your preferred MV* framework, bind a key-up event to a function that will publish the contents of the input field with every keystroke. One module should be listening for the topical message and perform the search. It should also use pub/sub to publish the results. Upon hearing the results, populate the unordered list by using your MV* framework.
You’ve accomplished a lot in this chapter. You’ve learned the following about inter-module interaction: