自定义指令无法在ng-repeat内编译

时间:2017-03-08 22:44:35

标签: angularjs angularjs-directive angularjs-compile

在ng-repeat中编译指令时,有人可以帮我解决范围问题吗?

https://plnkr.co/edit/y6gfpe01x3ya8zZ5QQpt?p=preview

自定义指令input-by-type可以使用基于变量类型的相应<div>替换<input> - 这在ng-repeat内使用之前一直正常。

正如您在plnkr示例中看到的那样,该指令按预期工作,直到它在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);
            };
        },
    };
}]);

如果我手动引用inputs[0]来编译input-by-type指令,它就可以正常工作:

<label>
    {{ inputs[0].name }}
    <div input-by-type="{{ inputs[0].type }}" name="myInputA" ng-model="data.A" ng-required="true"></div>
</label>

但是,当我将它包装在ng-repeat块中时,编译失败并出现一些意外的输出:

<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

实际输出:

Actual

2 个答案:

答案 0 :(得分:2)

postLink功能缺少elementattrs参数:

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);
            };
        },
    };
}]);

通过省略elementattrs参数,postLink函数创建了一个闭包,并使用element函数的attrscompile参数。即使$ compile服务使用正确的参数调用postLink函数,它们也会被忽略,而是使用编译阶段版本。

这会导致ng-repeat出现问题,因为它会克隆元素,以便将其附加到新的DOM元素。

答案 1 :(得分:0)

@ georgeawg的答案是正确的,但我遇到了第二个问题,我将在下面提出一个解决方案。

问题:ngModel不会按预期运行($pristine / $dirty等属性将不可用,也不会传播到容器formCtrl。)

为了解决这个问题,我按照了这个答案的建议:https://stackoverflow.com/a/21687744/1122851并改变了postLink编译元素的方式,如下所示:

var type = $interpolate(attrs.inputByType)(scope);
var html = inputs[type] || inputs.text;
var template = angular.element(html);
element.replaceWith(template);
$compile(template)(scope);

然后我意识到不再需要require: 'ngModel'scope: trueterminal: true(无论如何,它们都是我各种测试中的遗物)。最终代码:

app.directive('inputByType', ['$compile', '$interpolate', function($compile, $interpolate){
    return {
        restrict: 'A', // [attribute]
        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 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 template = angular.element(html);
                element.replaceWith(template);
                $compile(template)(scope);
            };
        },
    };
}]);

演示:https://plnkr.co/edit/ZB5wlTKr0g5pXkRTRmas?p=preview