Could anyone help me solve a scoping issue when compiling a directive within ng-repeat?
https://plnkr.co/edit/y6gfpe01x3ya8zZ5QQpt?p=preview
The custom directive input-by-type
can replace a <div>
with the appropriate <input>
based on a variable type - this works fine until used within an ng-repeat
.
As you can see on the plnkr example, the directive works as expected until it is used within ng-repeat
.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.inputs = [
{ name: 'Some Text', type: 'text', id: 1 },
{ name: 'EMail', type: 'email', id: 2 },
{ name: 'Age', type: 'number', id: 3 }
];
});
app.directive('inputByType', ['$compile', '$interpolate', function($compile, $interpolate){
return {
restrict: 'A', // [attribute]
require: '^ngModel',
scope: true,
compile: function(element, attrs, transclude){
var inputs = {
text: '<input type="text" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="...">',
email: '<input type="email" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="...@...">',
number: '<input type="number" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="###">',
};
return function(scope){
var type = $interpolate(attrs.inputByType)(scope); // Convert input-by-type="{{ some.type }}" into a useable value
var html = inputs[type] || inputs.text;
var e = $compile(html)(scope);
element.replaceWith(e);
console.log(type, html, element, e);
};
},
};
}]);
If I manually reference inputs[0]
to compile the input-by-type
directive, it works just fine:
<label>
{{ inputs[0].name }}
<div input-by-type="{{ inputs[0].type }}" name="myInputA" ng-model="data.A" ng-required="true"></div>
</label>
However, the moment I wrap this in an ng-repeat
block, the compile fails with some unexpected outputs:
<label ng-repeat="input in inputs">
{{ input.name }}
<div input-by-type="{{ input.type }}" name="myInput{{ $index }}" ng-model="data[input.id]" ng-required="true"></div>
</label>
Expected Output:
Actual Output:
The postLink function is missing element
and attrs
parameters:
app.directive('inputByType', ['$compile', '$interpolate', function($compile, $interpolate){
return {
restrict: 'A', // [attribute]
require: '^ngModel',
scope: true,
// terminal: true,
compile: function(element, attrs, transclude){
var inputs = {
text: '<input type="text" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="...">',
email: '<input type="email" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="...@...">',
number: '<input type="number" name="'+attrs.name+'" ng-model="'+attrs.ngModel+'" ng-disabled="'+attrs.ngDisabled+'" ng-required="'+attrs.ngRequired+'" placeholder="###">',
// image upload (redacted)
// file upload (redacted)
// date picker (redacted)
// color picker (redacted)
// boolean (redacted)
};
//return function(scope){
//USE postLink element, attrs
return function postLinkFn(scope, element, attrs) {
var type = $interpolate(attrs.inputByType)(scope); // Convert input-by-type="{{ some.type }}" into a useable value
var html = inputs[type] || inputs.text;
var e = $compile(html)(scope);
element.replaceWith(e);
console.log(type, html, element, e);
};
},
};
}]);
By omitting element
and attrs
parameters, the postLink function created a closure and used the element
and attrs
arguments of the compile
function. Even though the $compile service was invoking the postLink function with the proper arguments, they were being ignored and the compile phase versions were used instead.
This causes problems for ng-repeat
because it clones the element in order to append it to new DOM elements.