Three steps to building Angular apps with components
A tutorial on building a real world applications the right way with Angular 1.5 components.
In a previous post, I introduced Angular 1.5 Components as the new building blocks for Angular applications. Components have significant benefits compared to directives or routes with controllers plagued by scope inheritance woes.
In short, components have isolate scope, clearly declare their “one-way” bindings, have a default controller-as syntax, and most importantly, provide a separation of concerns between presentation and business logic.
There’s so much goodness around the idea of components, that they’ve become the building blocks of many modern web frameworks including Angular2, React, Vue.js, and now Angular 1.5.
“The right way” is a loaded term, so here’s what I mean by it. When I started my professional software engineering career, the language I cut my teeth on was python. Python has a couple tenants which guide most decisions in the language and an overall one being code is read more than it is written. Therefore, your code should be easy to read and as simple as possible to reason about. Components hit that nail on the head. You get a simple building block for business container components and presentational components. Here are my three steps to building apps with components.
Step one: everything you see is a component
To build with your components, you simply break down your page into logical sections, and turn those into components.
This is no different than React or Vue.js or Angular 2 prescribe. In fact, here’s the image from the Vue.js tutorial on how to build with components. Note the tree shape, it generally defines the paths of communication among components: parent-to-child, child-to-parent, and siblings.
So how do we build with these components?
Simple, make everything is a component! In this sample code I’m creating one root or app component to house the whole application and splitting up the three shared or common sections into their own components.
- app-header the top of the page header with logo, search bar, and profile links
- app-nav the sidebar navigation between sections of our app or page
- app-footer the page footer with copyright info, terms of service, privacy, about us, careers, …
- ui-view routed content, we’ll see how to route directly to components with ui-router
So why is this a win? Testing, reasonability, reusability.
Testing: Say we didn’t put the header as a component, how would you test that it displays properly? Of course we’ll add some selenium tests down the road to make sure the whole page loads and screenshots match up, but singling out the header would be a minor part that could be missed by the developer writing the tests or the code reviewers.
Reasonability: Having one <app-header> component describe the header is a win. It will have its own directory/module and its own tests. It can be further broken out into business and presentational components as needed and any new developer coming into the code, can immediately assume that app-header is a component which contains the header.
Reusability: Shared or common components, like the header, often aren’t as reusable as say a table or date picker component, but I think little work would be needed to reuse the header somewhere else.
For a quick comparison of how this looks in the real world, the angular 2 docs page could be built with the above component structure.
“The devil is in the details.” We know how to build the structural components, but how nested should component trees be? How do we route with components? And how do we send data from one component to another? First let’s look at a basic design paradigm the React world has brought to light: managing state by separating concerns between presentational and business logic or container components.
Step two: routing to components
The third-party ui-router beta release has made routing to components incredibly simple. Here’s the syntax:
To route to our profile component, we just use the component attribute of the state, and the other magic here is the router will automatically link the resolve to the bindings.
That may seem simple and unimportant until you look at the React world and see they must create a container component to fetch the data for a presentational component*. The router just did that for us.
We’ve separated our data-fetching and rendering concerns. — @learnreact https://medium.com/@learnreact/container-components-c0e67432e005#.leyjwxg4w
Step three: separating concerns
Dan Abramov, the author of React Hot Loader and Redux, wrote a wonderful reference for separating concerns of components in his article Presentational and Container Components. It’s worth the read and can’t be summarized without replicating it, but in general the approach is to keep state, data fetching, and action handling within a container component, and the DOM and state mutations within the presentational components.
Wait, state mutations within the presentation components? This is confusing since I just wrote, keep state in the container components. From what I can derive from the best practices the only state I ever mutate in a presentational component, is the local state pre-save or pre-submit.
For example, if you’re entering text in a new Todo in a Todo Form Component, then you’re mutating the state of the Form’s input element’s value, but the Todo Form Component shouldn’t update the app state with the new Todo when the user clicks the Add button. The Todo From should just fire an event callback. Here’s Todd Motto’s example of this in his Angular 1.x styleguide.
Here’s where we get to benefit from the React community and follow some of their best practices. I like to manage state from one of two places, a parent stateful component orchestrating the flow of data and callbacks among nested components.
Take this quick and dirty table component, because it doesn’t have any state, it could be getting its data from an API or from a local data store, and it doesn’t care. Likewise, the header click event could trigger a new fetch of data from the API with an order parameter, or have a container component or service sort the data.
- User components everywhere
- Route directly to components and let the router resolve their data fetches
- Separate presentational components from container (or stateful) components
*A larger React app will probably be using Redux, MobX, Relay, or some other Flux implementation to control asynchronous requests to fetch data for components. In that case, there may or may not be a need for a container component responsible for fetching the data.