| | |

Componentized Angular Development

Componentized Angular Development

Web components are arguably the Way Of The Future (tm). For better or for worse, and our experience says it's for the better, browsers are slowly going to start supporting native web components, changing the way web apps are thought about and made.

Web components are appealing because they promote reusability of code and encapsulation of logic with the view. Angular allows us to create native-looking web components with directives, but also allows standalone controllers to be made. The default pattern for registering a new route is to provide both the controller and the template. This causes a weird split between components the author initially thought weren't reusable, and components that are made from the ground up to be reusable.

We counter this by never having standalone controllers. We code in a way where any page/component is potentially reusable. It's tedious at first, but it turns into a really good practice. Our code is more uniform in layout and much easier to understand.

Our ui components are always directives, and there are two types - normal ui components and pages, which are put in a very explicit directory named pages in our folder structure. Pages are the same as any other ui-component, except they have routes registered with the router. When one of our bundles wants a certain page, they include the module, and automatically get the page added to the route.

In our app, directives also contain all the css and html they need in a commonjs module. Doing things this way helps future-proof our app by forcing us to structure our app like we were using native web components already. It would still be a significant task to port over to another framework/no framework, as we would have to excise all of the angular parts out and replace them, but our entire app is already laid out in such a way that we could keep the hierarchy of components as is.

This also has many other benefits that might be Bench-specific: In our app, we have different bundles for different types of users, so this is actually a great system for having bundles that only contain the routes and pages needed. Creating a completely new web-based tool for internal use is also a lot easier, because we can reuse the existing front-end code we wrote for our main application, including the directives, models, and styles.

This is a full example of the index.coffee file in a page component for handling client data – we use commonjs syntax for bundling our pages together with webpack.

require './client.scss'

deps = [
  require 'angular-ui-router'
  require('data/client').name
  require('data/address').name
  require('ui/binaryToggle').name
]

module.exports = angular.module 'bench.page.client', deps

.directive 'blClient', ->
  restrict: 'E'
  scope: {}
  bindToController: true
  template: require './client.html'
  controller: require './clientCtrl.coffee'
  controllerAs: 'vm'

.config ($stateProvider, $urlRouterProvider) ->
  $stateProvider
    .state 'clientPage',
      url: '/client/:id'
      template: '<bl-client></bl-client>'

If we want a brand new bundle with just that page, we just need to write a new entrypoint for webpack:

deps = [
  require('ui/webBase').name # base configurations and styles shared across all bundles
  require('page/client').name # a page
  require 'angular-ui-router' 
]

module.exports = angular.module 'bench.app', deps

.config ($stateProvider, $urlRouterProvider) ->
  $urlRouterProvider.otherwise '/defaultPage'

Our mobile app, even though it uses the Ionic framework, is also defined in the same fashion. Codebase size for mobile is not an issue because each bundle can pick and choose which components it wishes to include.

We found that this way of using webpack with angular reduces the decision tree of making a new page by a lot. Combined with our own data caching layer, each component/sub-component can be made without any awareness as to it’s parent or siblings, helping us concentrate on the code in front of us instead of having to worry about different side-effects from other components.

One disadvantage of this technique is how verbose it is. Directives quite a bit of boilerplate, as does using both commonjs and the angular module system together. We created a yeoman generator to make the process a lot cleaner for us, but it can still be annoying.

Another disadvantage is that it’s hard to fit all components and use-case into this ultra-compartmentized style. Exceptions have to be made, but they are rare and the code that ‘breaks the rule’ is self-contained and doesn’t affect other components globally.

Overall, structuring our app this way is worth the trouble for it’s advantages. It allows us to think a certain way, and not be afraid of trying new things with our pages. Our code feels easy to read and you can always find out what component is rendering what you're seeing on the page, so it's easy to find problems. Additionally, we found that after a while of building out the 'framework' of our app, we start just using what we've built and our resulting pages are easy to develop and consistent.

We'll be covering more of our front-end stack including our CSS styles and data layer in the coming weeks.

P.S. My co-worker, Jamie Woodbury, did a great presentation on this topic. Check it out here.