ukyoweb.com

Angularでradio-groupディレクティブを作る

これがやりたい。DEMO

<radio-group name="fruit" ng-model="fruit">
  <radio ng-value="1">apple</radio>
  <radio ng-value="2">banana</radio>
</radio-group>

こうする。

var app = angular.module('radio-directive', []);

app.directive('radioGroup', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      name: '@'
    },
    require: 'ngModel',
    transclude: true,
    template: '<ul ng-transclude></ul>',
    controller: function($scope, $element, $attrs, $transclude) {
      // https://coderwall.com/p/ngisma
      $scope.safeApply = function(fn) {
        var phase = this.$root.$$phase;
        if(phase == '$apply' || phase == '$digest') {
          if(fn && (typeof(fn) === 'function')) {
            fn();
          }
        } else {
          this.$apply(fn);
        }
      };

      var ngModelCtrl = $element.controller('ngModel');
      var radios = [];
      var select;

      this.name = $scope.name;
      this.addRadio = function(scope) {
        radios.push(scope);
      };

      select = this.select = function(value) {
        radios.some(function(r) {
          if (r.ngValue === value) {
            r.$element.find('input').attr('checked', 'checked');
          }
        });
        ngModelCtrl.$setViewValue(value);
        $scope.safeApply();
      };

      ngModelCtrl.$render = function() {
        select(ngModelCtrl.$viewValue);
      };
    }
  };
});

app.directive('radio', function($parse) {
  return {
    restrict: 'E',
    replace: true,
    require: '^radioGroup',
    scope: {
      ngValue: '=',
      value: '@'
    },
    transclude: true,
    template:
      '<li>' +
        '<input name="{{name}}" type="radio" id="radio-{{$id}}">' +
        '<label for="radio-{{$id}}" ng-transclude></label>' +
      '</li>',
    link: function($scope, $element, $attrs, radioGroupCtrl, $transclude) {
      var ngClick;

      if ($attrs.ngClick) {
        ngClick = $parse($attrs.ngClick);
      }

      $scope.name = radioGroupCtrl.name;
      $scope.$element = $element;
      radioGroupCtrl.addRadio($scope);
      $element.find('input').on('click', function() {
        radioGroupCtrl.select($scope.ngValue);
        if (ngClick) {
          // ng-clickは外のスコープのコンテキストで実行してやる。
          ngClick($scope.$parent.$parent);
        }
      });
    }
  };
});