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

Custom Directive fails to compile within ng-repeat

谁能帮我解决在 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>

预期输出:


实际输出:

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 函数创建了一个闭包并使用了 compile 函数的 elementattrs 参数。即使 $compile 服务使用正确的参数调用 postLink 函数,它们也会被忽略,而是使用编译阶段版本。

这会给 ng-repeat 带来问题,因为它会克隆元素以便将其附加到新的 DOM 元素。

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

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

为了解决这个问题,我遵循了这个答案的建议: 并更改了 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