Angular.js custom filters in separate modules

development

Angular.js custom filters in separate modules

I’ve been spending a lot of time recently with Angular.js, both in my own explorations and in my daily work.

I have been searching for the best method of modularizing an Angular application to aid in testing, development and future maintenance.

I, like many who will read this, started out in Angular creating applications in one index.html file or maybe an index.html with an app.js. As exciting as these quick applications can be to build and experiment with, they do not scale and really should not be used in production and definitely not in an actively developed business critical application.

There are some great resources for style and architecture guides out there and it only takes a quick google search of ‘angular.js style guide’ to find them. Two that I’ve been consulting regularly are Todd Motto’s and John Papa’s both of which are actively updated on GitHub. I definitely recommend checking them out.

I’ve used these guides along with other resources to build a large-scale demo Angular.js application on my website, which is also being tracked on GitHub.

Now to the point of this post…

My application (running on Angular.js v1.2.13) is currently divide into multiple modules and the ones that matter for this post are displayed below.

mc.controllers.js

(function () {
  'use strict';

  var controllersModule = angular.module('app.mc.controllers', [
    'multi-select',
    'app.mc.directives',
    'app.mc.services',
    'app.mc.filters',
  ]);

  controllersModule.controller('mcController', mcController);
  mcController.$inject = ['$window', '$scope', 'mcDataService'];

  function mcController($window, $scope, mcDataService) {
    var vm = this;
    vm.collection = null;
    vm.filteredCollection = null;
    vm.searchText = null;
    vm.searchOptions = getSearchOptions();
    vm.searchBy = vm.searchOptions[0];

    // .....
    $scope.$watchCollection(
      'vm.collection | textFilter:vm.searchText:vm.searchBy',
      function (newVal) {
        vm.filteredCollection = newVal;
        vm.collectionYears = getYearsFromCollection(vm.filteredCollection);
      },
      true
    );
  }
})();

mc.filters.js

(function () {
    'use strict';

    var filtersModule = angular.module('app.mc.filters', []);

    filtersModule.filter('textFilter', textFilter);
    textFilter.$inject = [];

    /*
     * Filter for comparing name of artist in a collection of releases to given text value
     */
    function textFilter($filter) {
        return function (items, text, searchBy) {
            ...
        };
    }
}());

So here we have two modules, one containing filters and one containing controllers.

Both of these modules are named so as to be sub-modules of a parent app.mc (not shown).

If you will make note of the highlighted lines above you will see that my controllers module is named app.mc.controllers and my controller is named mcController. My filters module is named app.mc.filters and the filter itself is named textFilter.

When I was first attempting to use textFilter in the $watchCollection statement of my controller I thought I needed to inject it into my mcController function along with the other dependencies. I injected the module the filter is defined in (app.mc.filters) into the module my controller is defined in (app.mc.controllers). But this step appears to be all that is needed!

Unlike my service mcDataService, I do not need to inject the filter name into the function that defines my controller. I was trying to do this for an hour, receiving an Error: [$injector:unpr] Unknown provider: textFilterProvider <- textFilter error. I thought maybe there was a typo somewhere in my dependencies or module definitions.

Nope! Angular does not require the name of the filter to be injected into the controller to use it there. Simply inject the module the filter is in into the module the controller is in and you are golden.

I’m not quite sure why this is the case but if anyone could give me an explanation, it would be appreciated.