Exploring better way to extend controller in AngularJS with CoffeeScript

TL; DR

  • exploring ways to extend controller
  • disclaimer: it's in CoffeeScript

In Angular, it's always good practice to split up and refactor non-view related logic into Services and break up re-usable view-related logic into components. But once in a while, it can still get out of control when a view has convoluted logic.

This is exactly the case that I'm running into. Roughly half of my controller is based on how the user navigates while the other half is based on server reponse when resources change. However, at the same time, I also have a different view for read-only user, which means, I have to duplicate the same navigation code in two places in our codebase.

I started a quest to explore a better way to extend controller in Angular. My goal is to have the navigation related code only in one parent controller and allow the code to be reused in multiple places.

This is what I ended up doing:

  • use factory to return a base controller class
  • in main controller, extend the base controller with customization
  • pass in scope/vm into customized class

The scaffolding app is generated with generator-gulp-angular and you can take a look at the repo here to compare.

Here's what my MainController looks like:

angular.module 'coffeeBaseCtrlTest'  
  .controller 'MainController', (BaseController) ->
    'ngInject'
    vm = this

    class MainController extends BaseController
      activate: ->
        console.log 'activate from MainController'
        super

    mainCtrl = new MainController(vm)
    mainCtrl.activate()

Once MainController extends BaseController, it has all the methods defined in BaseController, and this is advantageous because it allows you to call super and use the methods from BaseController

Here's what the BaseController factory looks like:

angular.module 'coffeeBaseCtrlTest'  
  .factory 'BaseController', ($timeout, webDevTec, toastr) ->
    'ngInject'

    class BaseController
      constructor: (vm) ->
        @vm = vm
        @vm.awesomeThings  ?= []
        @vm.classAnimation ?= ''
        @vm.creationDate   ?= 1453658664761
        @vm.showToastr      = @showToastr

      activate: ->
        console.log 'activate from BaseController'
        @getWebDevTec()
        $timeout (=>
          @vm.classAnimation = 'rubberBand'
          return
        ), 4000
        return

      getWebDevTec: ->
        @vm.awesomeThings = webDevTec.getTec()
        angular.forEach @vm.awesomeThings, (awesomeThing) ->
          awesomeThing.rank = Math.random()
          return
        return

      showToastr: =>
        toastr.info 'Fork <a href="https://github.com/Swiip/generator-gulp-angular" target="_blank"><b>generator-gulp-angular</b></a>'
        @vm.classAnimation = ''
        return

    BaseController

The advantages of doing it this way are:

  • reduces code by the use of super in classes
  • it's easy to see in the constructor what's really binding to the vm

I'm sure there's a better way out there to write this, but right now this seems to be an easier approach without modifying too much of the codebase. After all, in my experience, it's not that common to have a need to extend from a parent controller.

Here's a different approach to extract base controller in CoffeeScript:
angularCoffeeBase

Thanks for reading and suggestions are welcome : )

comments powered by Disqus