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

More than 5 years have passed since last update.

追記: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;
});
}
}
});


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