LoginSignup
35
37

More than 5 years have passed since last update.

自作directive内でng-modelをきちんと動作させる

Last updated at Posted at 2013-12-05

追記:require: '^ngModel'で解決しました。

自作directive内でng-modelを使ってみました。

デモ:Plunker - ng-model in a my directive.

作ってみるとわかりますが、自分で作ったdirectiveからng-modelを使おうとおもったらけっこう大変!
なんとisolated scope内では親スコープのngModelControllerを参照できないんですね。
ただ、isolated scopeにしないとせっかくのAngularJSでテンプレートが使えないという(複数個要素を作ると干渉するので)。

これを解決するためにscope.$new()で子スコープを作って、$compileを使ってテンプレートを手動でコンパイルした上で、さらに手動で親と子をバインドしてみました(以下コード例)。面倒くさいですが、現状わかっている範囲だと、こんなかんじで実装しないとng-changeが動かなかったり、ngModelControllerの便利なメソッドも使えないのでこれがベストかと思われます。

修正前

app.js
var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
  $scope.onChange = function () {
    console.log('name is changed!');
  };
  $scope.onChange2 = function () {
    console.log('name is changed!!');
  }
  $scope.onChange3 = function () {
    console.log('name is changed!!!');
  }
});

app.directive('hoge', function ($compile) {
  var template = '<label>{{label}}</label><input ng-model="model" type="text">';
  return {
    restrict: 'E',
    replace: true,
    require: '?ngModel',
    template: '<div></div>',
    link: function (scope, element, attrs, ngModelCtrl) {
      // 子スコープを作って
      var child = scope.$new();
      // テンプレートをコンパイルして
      element.append($compile(template)(child));
      attrs.$observe('label', function() {
        child.label = attrs.label;
      });
      // 親から子にバインドして
      scope.$watch(attrs.ngModel, function (value) {
        child.model = value;
      });
      // 子から親にバインドする
      child.$watch('model', function (value) {
        ngModelCtrl.$setViewValue(value);
      });
    }
  }
});
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.3/angular.js" data-semver="1.2.3"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="plunker">
    <div ng-controller="MainCtrl">
      <input ng-model="name" ng-change="onChange()">
      <p>Hello {{name}}!</p>
      <hoge ng-model="name" label="{{name}}" ng-change="onChange2()"></hoge>
      <hoge ng-model="name" label="Bar" ng-change="onChange3()"></hoge>
    </div>
  </body>
</html>

追記

require: 'ngModel'のところをrequire: '^ngModel'にすることで親ディレクティブのコントローラーを参照できるようです。よくみたらチュートリアルに書いてあったという。

app.js
app.directive('hoge', function() {
  return {
    restrict: 'E',
    require: '^ngModel',
    scope: {
      label: '@'
    },
    template: '<div><label>{{label}}</label><input ng-model="model"></div>',
    replace: true,
    link: function(scope, element, attrs, ctrl) {
      scope.$watch('model', function (value) {
        ctrl.$setViewValue(value);
      });

      scope.$parent.$watch(attrs.ngModel, function (value) {
        scope.model = value;
      });
    }
  }
});

・・・ずいぶん簡単になりました。

35
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
37