Last Updated: February 25, 2016
·
1.864K
· tastycode

Making a simple custom element in angularjs

While converting a Rails application to Angularjs. I ran across a select input that loaded some US state names from a yaml file.

.control-container= state_selector form, :"#{type}state", {}, :validate => validate, :class => %w(control--full-line)

These states come from a yaml file indirectly

def state_selector(form, field_name, options={ }, html_options={ })
  states = [['', '']]
  states += Countries.us_states.sort.map { |us_state_code, _| [us_state_code, us_state_code] }
  form.select field_name, states, options, html_options
end

I converted the yaml to json and embedded it into an angular controller for this form. Here is the first pass at a conversion to angular from a rails helper.

 <label class="above-field" for="state">State</label>
<select class="field" name="state" ng-model="payment.state" ng-options="state.abbreviation as state.name for state in states" required>
$scope.states = function(rawStates) { 
  return _.map(rawStates, function(abbreviation, name) {
    return {abbreviation: abbreviation, name: name};
  });
}({
  "CA": "CALIFORNIA", 
  // the rest of the states
  "NY": "NEW YORK"
});

This is tedious to embed in every page I might need a list of states on. The quickest way around this is to extract the states into a value provider.

angular.module('canHazApp')
  .value('states', 
    function(rawStates) { 
      return _.map(rawStates, function(abbreviation, name) {
        return {abbreviation: abbreviation, name: name};
      });
    }({
        "AK": "ALASKA", 
      // the rest of the states
      "WY": "WYOMING"
    }));

then in app/scripts/controllers/contact_form.js

angular.module('canHazApp')
    .controller('ContactFormCtrl', function($scope, states) { //inject the value
       //lots of code
       $scope.states = states
});

Now we have it so our states would not be duplicated if we decided to pull in states in another control somewhere else. However, we would have to add states to the dependencies of every controller that needs the state list.

We can extract this further out to a directive so that we do not have to define states on the scope of the select element. We remove states from contact_form.js and move the dependency into a directive.

angular.module('canHazApp')
  .directive('stateOptions', function (states) { //states value injected into directive context
    return {
      restrict: 'E',
      replace: true,
      scope: true,  //we want a separate child scope
      template: '<select ng-options="state.abbreviation as state.name for state in states"></select>',
      require: '^ngModel',
      link: function(scope, element, attrs) {
        scope.states = states;
      }
    };
  });

now we can use our directive any where within the app, and get our list of states as the options for select.

<label class="above-field" for="state">State</label>
<state-options class="field" name="state" ng-model="payment.state" required></state-options>