AngularJS
ngModelControllerを使用するディレクティブ
サーチ…
簡単なコントロール:評価
以下のように使用するためのシンプルなコントロール、評価ウィジェットを作ってみましょう:
<rating min="0" max="5" nullifier="true" ng-model="data.rating"></rating>
今はファンシーなCSSはありません。これは次のように表示されます:
0 1 2 3 4 5 x
数字をクリックすると、その評価が選択されます。 「x」をクリックすると評価がnullに設定されます。
app.directive('rating', function() {
function RatingController() {
this._ngModel = null;
this.rating = null;
this.options = null;
this.min = typeof this.min === 'number' ? this.min : 1;
this.max = typeof this.max === 'number' ? this.max : 5;
}
RatingController.prototype.setNgModel = function(ngModel) {
this._ngModel = ngModel;
if( ngModel ) {
// KEY POINT 1
ngModel.$render = this._render.bind(this);
}
};
RatingController.prototype._render = function() {
this.rating = this._ngModel.$viewValue != null ? this._ngModel.$viewValue : -Number.MAX_VALUE;
};
RatingController.prototype._calculateOptions = function() {
if( this.min == null || this.max == null ) {
this.options = [];
}
else {
this.options = new Array(this.max - this.min + 1);
for( var i=0; i < this.options.length; i++ ) {
this.options[i] = this.min + i;
}
}
};
RatingController.prototype.setValue = function(val) {
this.rating = val;
// KEY POINT 2
this._ngModel.$setViewValue(val);
};
// KEY POINT 3
Object.defineProperty(RatingController.prototype, 'min', {
get: function() {
return this._min;
},
set: function(val) {
this._min = val;
this._calculateOptions();
}
});
Object.defineProperty(RatingController.prototype, 'max', {
get: function() {
return this._max;
},
set: function(val) {
this._max = val;
this._calculateOptions();
}
});
return {
restrict: 'E',
scope: {
// KEY POINT 3
min: '<?',
max: '<?',
nullifier: '<?'
},
bindToController: true,
controllerAs: 'ctrl',
controller: RatingController,
require: ['rating', 'ngModel'],
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setNgModel(ctrls[1]);
},
template:
'<span ng-repeat="o in ctrl.options" href="#" class="rating-option" ng-class="{\'rating-option-active\': o <= ctrl.rating}" ng-click="ctrl.setValue(o)">{{ o }}</span>' +
'<span ng-if="ctrl.nullifier" ng-click="ctrl.setValue(null)" class="rating-nullifier">✖</span>'
};
});
キーポイント:
-
ngModel.$render
を実装して、モデルのビュー値をビューに転送します。 - ビューの値を更新する必要があると感じるたびに、
ngModel.$setViewValue()
呼び出します。 - もちろん、コントロールをパラメータ化することもできます。入力 - 片方向バインディングを明確に示すために、角度> = 1.5の場合は、パラメータ
'<'
スコープバインディングを'<'
使用してください。パラメータが変更されたときにアクションを実行する必要がある場合は、JavaScriptプロパティ(Object.defineProperty()
参照)を使用していくつかの時計を保存できます。
注1:実装を過度に複雑化させないために、評価値は配列に挿入されます( ctrl.options
。これは必要ではありません。より効率的ですが、より複雑な実装では、 min
/ max
変更時にDOM操作を使用して評価を挿入/削除できます。
注2: '<'
スコープバインディングを除いて、この例はAngular <1.5で使用できます。 Angular> = 1.5の場合は、これをコンポーネントに変換し、コントローラのコンストラクタではなく、 $onInit()
ライフサイクルフックを使用してmin
およびmax
を初期化することをお勧めします。
そして必要な手伝い: https : //jsfiddle.net/h81mgxma/
いくつかの複雑なコントロール:完全なオブジェクトを編集する
カスタムコントロールは、プリミティブのような些細なものに限定する必要はありません。より興味深いものを編集することができます。ここでは、編集者用と編集用の2種類のカスタムコントロールを紹介します。アドレスコントロールは、人の住所を編集するために使用されます。使用例は次のとおりです。
<input-person ng-model="data.thePerson"></input-person>
<input-address ng-model="data.thePerson.address"></input-address>
この例のモデルは意図的に単純化されています。
function Person(data) {
data = data || {};
this.name = data.name;
this.address = data.address ? new Address(data.address) : null;
}
function Address(data) {
data = data || {};
this.street = data.street;
this.number = data.number;
}
アドレスエディタ:
app.directive('inputAddress', function() {
InputAddressController.$inject = ['$scope'];
function InputAddressController($scope) {
this.$scope = $scope;
this._ngModel = null;
this.value = null;
this._unwatch = angular.noop;
}
InputAddressController.prototype.setNgModel = function(ngModel) {
this._ngModel = ngModel;
if( ngModel ) {
// KEY POINT 3
ngModel.$render = this._render.bind(this);
}
};
InputAddressController.prototype._makeWatch = function() {
// KEY POINT 1
this._unwatch = this.$scope.$watchCollection(
(function() {
return this.value;
}).bind(this),
(function(newval, oldval) {
if( newval !== oldval ) { // skip the initial trigger
this._ngModel.$setViewValue(newval !== null ? new Address(newval) : null);
}
}).bind(this)
);
};
InputAddressController.prototype._render = function() {
// KEY POINT 2
this._unwatch();
this.value = this._ngModel.$viewValue ? new Address(this._ngModel.$viewValue) : null;
this._makeWatch();
};
return {
restrict: 'E',
scope: {},
bindToController: true,
controllerAs: 'ctrl',
controller: InputAddressController,
require: ['inputAddress', 'ngModel'],
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setNgModel(ctrls[1]);
},
template:
'<div>' +
'<label><span>Street:</span><input type="text" ng-model="ctrl.value.street" /></label>' +
'<label><span>Number:</span><input type="text" ng-model="ctrl.value.number" /></label>' +
'</div>'
};
});
キーポイント:
- 私たちはオブジェクトを編集しています。私たちは親から私たちに与えられたオブジェクトを直接変更したくはありません(私たちのモデルは不変性の原則に適合するようにしたい)。そこで、編集中のオブジェクトに対して浅い時計を作成し、プロパティが変更されるたびに
$setViewValue()
モデルを更新します。私たちは親にコピーを渡します 。 - モデルが外部から変更されるときはいつでも、それをコピーしてコピーを私たちのスコープに保存します。不変性の原則は、内部のコピーは不変ではありませんが、外部は非常にうまくいく可能性があります。さらに、ウォッチャーがモデルによってプッシュされた変更をトリガするのを避けるため、ウォッチ(
this_unwatch();this._makeWatch();
)を再構築します。 (UIで行われた変更に対してのみ時計がトリガーされるようにします)。 - 上記の点以外は、
ngModel.$render()
を実装し、ngModel.$setViewValue()
を単純なコントロールとngModel.$setViewValue()
ように呼び出しngModel.$render()
評価例を参照)。
人のカスタムコントロールのコードはほぼ同じです。テンプレートは<input-address>
を使用してい<input-address>
。より高度な実装では、再利用可能なモジュールでコントローラを抽出することができました。
app.directive('inputPerson', function() {
InputPersonController.$inject = ['$scope'];
function InputPersonController($scope) {
this.$scope = $scope;
this._ngModel = null;
this.value = null;
this._unwatch = angular.noop;
}
InputPersonController.prototype.setNgModel = function(ngModel) {
this._ngModel = ngModel;
if( ngModel ) {
ngModel.$render = this._render.bind(this);
}
};
InputPersonController.prototype._makeWatch = function() {
this._unwatch = this.$scope.$watchCollection(
(function() {
return this.value;
}).bind(this),
(function(newval, oldval) {
if( newval !== oldval ) { // skip the initial trigger
this._ngModel.$setViewValue(newval !== null ? new Person(newval) : null);
}
}).bind(this)
);
};
InputPersonController.prototype._render = function() {
this._unwatch();
this.value = this._ngModel.$viewValue ? new Person(this._ngModel.$viewValue) : null;
this._makeWatch();
};
return {
restrict: 'E',
scope: {},
bindToController: true,
controllerAs: 'ctrl',
controller: InputPersonController,
require: ['inputPerson', 'ngModel'],
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setNgModel(ctrls[1]);
},
template:
'<div>' +
'<label><span>Name:</span><input type="text" ng-model="ctrl.value.name" /></label>' +
'<input-address ng-model="ctrl.value.address"></input-address>' +
'</div>'
};
});
注:ここでオブジェクトは型付けされています。つまり、適切なコンストラクタがあります。これは必須ではありません。モデルは単純なJSONオブジェクトにすることができます。この場合は、コンストラクタの代わりにangular.copy()
使用してangular.copy()
。追加の利点は、コントローラが2つのコントロールで同一になり、共通のモジュールに簡単に抽出できることです。
フィドル: https : //jsfiddle.net/3tzyqfko/2/
コントローラーの共通コードを抽出した2つのバージョンのフィドル: https : //jsfiddle.net/agj4cp0e/とhttps://jsfiddle.net/ugb6Lw8b/