Todd Motto has been writing a bunch about Angular components recently and I’ve finally had the chance to try out components to see if they live up to the hype.
Can we now create reusable, easily tested HTML and JS components with Angular?
Can we get the reasonability that React has without the need to bind all our event handling functions with this?
Can we replace routing templates and controllers with components, even ones resolving data asynchronously from a service?
A lot of articles coming from the Angular community will tell you that they’re the path to Angular 2, but from my perspective they leave behind the confusing parts of directives and gain the lessons learned from React.
It is now straitforward to create stateless components with a one-way data flow. These are reusable, testable, and declarative. Here’s how you could create a nice welcome message for your users.
The above component can simply be used like a directive.
I’ve skipped over a couple important notes, like where is the controller, what does ‘<’ mean and why is bindings used instead of scope. These are better covered in other posts, but in short, if you don’t name a controller, which is a goal for a stateless component, then Angular will use a default of $ctrl. The less than symbol has also been added to components and directives to mean a one-way data flow, although in practice if you’re binding to an object, be wary because a child component can mutate it, even if it’s a one-way binding.
And that’s the short of it!
You can test components the same way you test directives:
- compile an angular element
- attach data to the element’s scope
- notify angular of the scope’s data with $apply()
- test the elements produced text or HTML
Angular also now provides a $componentController as part of ngMock to, you guessed it, test component controllers. This can be used to test event handling functions and that any data processing is handled properly, as is normally the case with the lifecycle events built into components ($onInit, $onChanges, $onDestroy). In our case, we can just test that the data is on the scope.
Pat on the back, we’ve tested our component, now let’s wire it up to a route.
Using components with routes
When I was first learning React, I came across a great post describing how to build a typical app or page with React by Pete Hunt. What it provided was the pattern to building any webpage with reusable pieces, in this case React components. Either way, it’s components all the way down.
With Angular client-side routing, or the third party router ui-router, the standard has been to declare a route path (or name or url), a controller, a template, and any data that should be dependency injected prior to the controller executing, this data is the angular resolve. Simple right! Then once you have your data and the controller is executed the route template is processed and you have your page.
In my experience, having the controller and template loaded this way, with the freedom controllers have to inherit scope from parent controllers and handle any type of event breaks any hope of having a clean or minimal controller, as it’s the easiest place to get stuff done. Creating a new directive requires a lot of mental preparation unless you’re writing them every day or have some great templates to go off of. But I think components, just like with building out a page with React, will prove easy enough to get into our muscle memory.
As an aside, Todd Motto gives a couple ideas on how we could structure our code to tack that resolve code onto the route’s controller’s class. I haven’t gone down the rabbit hole of classes with Angular 1.5 and probably won’t, so for now the routing code and data resolving code are separated.
A user can go to the route ‘/hello’, the user’s data will be fetched from an API call and then injected into the route’s controller and scope and we’re putting that strait into our hello nav component. It should be a little clear or amazing that we have two levels of abstraction without an explicit controller. No need to bind the resolve ‘me’ onto a controller scope thanks to Angular automatically adding the resolve to it for us. If that’s too much magic for you, you can always bind this to your controllers manually.
N.B. if you’re using ui-router, you can achieve the same gain but in a slightly different manner.
The UX cost of resolve
But sometimes, often, always, there is latency between the user’s browser client and your server in the cloud. If we have a resolve, the route change is prevented from loading until 1. all the requested data from the resolves have resolved, and 2. the controller code finishes executing. You can make your controller load as fast as possible, but some API calls and queries are going to take their sweet time.
So how long is too long? Google IO gave a slew of talks on performance which help define this. In my opinion, you want the user to know they clicked something within 50ms and the route change should complete within 200ms.
I didn’t say all the data needs to be resolved in 200ms, but the route change should happen, or part of it. So my rule of thumb is fetch all the data I can in under 200ms, then defer the rest and load it asynchronously after the page is rendered. You can use font awesome spinners, etc, to show that some part of the page is still loading, and the users will thank you for that. What we don’t want is text flickering.
Resolving data from a component
Components have some lifecycle hooks, we can use those. It will cause text flickering if you’re loading data to display as text, but you’ll be able to encapsulate the data fetching within the component that needs the data and away from the route.
So this component is a little more complex, it fetches data from a service, and then changes the DOM depending on if it has the data or not. It’s missing any error handling notification and testing now requires spying on the Me service’s query.
We could make this a little better. After all we do want to reuse components…
I’m in favor of building as many stateless components as possible and composing pages from them. Why? They’re easy to test, easy to reason about, and therefore easy to reuse.
If we need state, it’s there for us, even if multiple components depend on shared state.
I’m glad the Angular team is providing 1.5 developers with a component solution and will see how it plays out in refactoring some complex views.
If you’re interesting in seeing how a big app could be built in Angular 1.5 with modules, components, and the new JS classes, check out Todd Motto’s style guide. I like 90% of it, but I would still forgo using classes unless I know what I’m working on will be migrated to Angular 2, which uses classes heavily. The downside of using classes is it forces the developer to add a build step or tooling that might not be present to compile classes, which are not natively supported by most browsers.
Added July 25, 2016: There was an excellent article published last week describing building an production quality app using an Angular 1.5 component architecture with ES6 modules. Here’s the link: http://blog.grossman.io/angular-1-component-based-architecture-2/