Chapter 9. Refactoring to Patterns

Modern JavaScript frameworks have enabled a new style of writing web applications, but patterns and practices from earlier technologies have led us to make suboptimal design choices in our applications.

The patterns used for the previous generations of web applications are outdated, but old habits die hard. The patterns that worked well for server-side web apps don’t fit very well into the single-page paradigm. JavaScript enables the user’s web browser to do much of the work we used to do on the server, but now we need to start thinking in terms of components, smaller bits of functionality that create an experience for the user.

Angular is sometimes called a model-view-whatever (MVW or MV*) framework, but actually its support for models offers developers very little help. As a result, anemic models, i.e., plain JSON, are the default choice for model patterns. Behavior is added through a service (which is not great) or through a controller (even worse).

Two-way data binding is amazing compared to the way data binding worked in previous frameworks. Two-way binding allows form fields and DOM elements to be instantaneously and effortlessly synchronized. Angular promised us power and simplicity with two-way binding, but as our use cases grow in complexity, our code actually becomes more difficult to reason about and is less performant. The unidirectional data flow architecture popularized by React offers us a clearer pattern that we can immediately use in our Angular applications.

Rich Domain Models Locate Behavior on the Object Itself

Binding JSON to a view works well with pure data, but often models need to express behavior or computed properties. This is where a rich domain model is handy.

A rich domain object is created from a JSON object using a vanilla JavaScript factory pattern. A factory pattern is a function that takes some parameters and uses them to create a new object. JSON has no way to preserve functions; using this creational pattern is a way to add behavior and computed properties to the object itself.

You would like to get as much value as possible for the amount of effort spent writing front-end logic for correctly representing data to your users. Therefore, why couple your model logic to your presentation framework if you can possibly avoid it? The same rich domain models you write for an Angular application can easily be reused in a different front-end library or even on a server-side node application. The code for your rich domain models can be managed apart from your Angular application. Just use a thin Angular wrapper to use these models as a service.

function Movie (json) {
  var movie  = {
    title: json.title || 'no title',
    times: json.movieTimes,
    nextShowingTime: function nextShowing () {
      // logic to determine the time of the next showing
    }
  };
  return movie;
}

// later in MovieService
function create (json) {
  return new Movie(json);
}

// later in a view template
<span>Next showing: {{ movie.nextShowingTime() }}</span>

This example demonstrates an application that displays a list of currently playing movies. The view displays the next showtime based on the time the component is loaded.

Rich data models allow the business logic to be located as closely as possible to the model itself and provide an opportunity to create dynamic properties not present in the original data. For example, with a JSON model of a client, including firstName, lastName, and gender, you can write a dynamic formalGreeting property that uses all three properties.

ngResource uses this pattern to generate models that are bound to a RESTful server resource, but the models produced by ngResource only provide basic functionality. Compared to the effort needed to customize an ngResource-based model, it’s probably easier to write custom services or use a more powerful library like Restangular.

Flux Pattern: Unidirectional Data Flows

In 2015, Facebook’s React.js challenged Angular as the most popular front-end libary. React is a library for writing JavaScript components, not entire applications. Its declarative design and custom templating language (JSX), along with state management libraries such as Flux and Redux, have made a convincing argument in favor of a much smaller API than Angular.

Angular’s two-way data binding and digest cycle is difficult to learn. Although it seems like magic at first, developers quickly run into technical obstacles in Angular while integrating older third-party libraries or when trying to manage complex states, especially between components. React avoids this problem by using a unidirectional data flow instead of a bidirectional flow like Angular.

The flux pattern works by keeping model data in a service called a store. Only the store may modify the state of the data. The store exposes a read-only interface of the data that is bound to the view template. When the UI needs to act on that data, it sends an action to a dispatcher, a service that broadcasts actions to listeners on the store.

In the flux pattern there is only one way that data can change state. This makes reasoning about the application much easier. Did something change? We can always trace the cause. Do we need the view to update? We just listen for the change event and re-render the view. Compare this pattern to Angular’s two-way binding. In Angular, any value bound to the $scope can be modified by any function that can see it. Angular then depends on dirty checking to determine if that value has changed. When a change happens, it executes any $watch callbacks set on that data. This process is opaque to the developer, and besides is not very performant.

The flux pattern is not built in to Angular, but there are libraries available to adapt the two most popular flux libraries for Angular (flux-angular and angular-redux). Since the pattern is so simple, it’s straightforward to write your own version for your application.

Aside from the flux pattern, Angular has adopted the unidirectional flow in the AngularJS component() method using one-way controller binding. Data inputs are bound to a component controller using the less-than sign <. When data changes, the controller $onChanges() event fires with information about the change. With this binding technique it’s no longer necessary to $watch inputs inside a directive.

Page Controller Is an Antipattern in Angular

Anachronistic paradigms of web app development are leading to mistakes in modern app architectures. Before the advent of single-page applications it made sense to represent each feature of the application as a screen or a page because user interactions would cause the browser to make a round-trip to the server and reload the page. A page controller on the server would send commands to update or change a model associated with that page.

Single-page applications don’t need page controllers because they can interact with a server via AJAX without needing to reload the page. When single-page applications (SPAs) are designed to use page controllers, those controllers end up having far too many and often misplaced responsibilities. Features that should load once as part of the application shell get unloaded and reloaded each time a controller and view switches.

Instead of using a page controller, use a lightweight application shell. A shell holds the minimum application state, including headers (e.g., branding), navigation, footers, and whatever application logic is needed for bootstrapping the application. The shell lacks any of the app’s main features. The features should be implemented as composable components.

To avoid accidentally designing page controllers in a single-page app, architects need to consider the application as a set of interacting modules instead of whole screens or pages. Opportunities for reuse should be identified as early as possible, and components should be designed with clear and simple interfaces for interacting with other components. Encourage taxonomy such as route or feature instead of screen and page. Once you start thinking of a feature as a page, it’s a short road to designing it as though it were a page.

Summary

In this section we covered:

  • Using rich domain objects to locate our business logic on models so that it’s easier to associate model behavior with UI bindings

  • How we can apply lessons from the popular React library to our Angular applications via unidirectional data flows

  • How the page controller pattern migrated to single-page applications, and why this is an antipattern for Angular applications, and what to do about it

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

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