2

我的目标 - 指令 dir2 用指令 dir1 替换自身,而指令 dir1 又用输入替换。

但是,在通过输入替换 dir1 期间,我在 replaceWith 函数中得到 parent is null 异常。

为同样的小提琴

var app = angular.module("myapp",[]);

function MyCtrlr($scope){
    $scope.vars = {val:"xyz"};
}

app.directive("dir2", function($compile){
    return {
        restrict : 'E',
        replace : true,
        compile :function(el, attrs) {
            var newhtml =  '<dir1 field="' + attrs.field + '" />';
            return function(scope, el, attrs) {
                console.log('dir2 parent = ' + el.parent());
                el.replaceWith($compile(newhtml)(scope));
            }
        }
    }
});

app.directive("dir1", function($compile){
    return {
        restrict : 'E',
        replace : true,
        compile :function(el, attrs) {
            return function(scope, el, attrs) {
                console.log('dir1 parent = ' + el.parent());
                console.log(scope.field);
                el.replaceWith($compile('<input type="text" ng-model="' + attrs.field + '.val" />')(scope));
            }
        }
    }
});
4

3 回答 3

1

基本上您会收到错误消息,因为编译过程分两个阶段进行:compilelink. 由于您的指令正在同时编译(第一阶段),当dir2完成编译时,DOM 元素dir1还没有准备好进行操作。

因此,我已更改dir1为使用流程的链接阶段(第二阶段)。

像这样dir2有机会完成并创建 DOM 元素(模板)dir1

http://plnkr.co/edit/GrOPkNaxOxcXFDZfDwWh

 <!doctype html>
 <html lang="en" ng-app="myApp">
 <head>
 <meta charset="UTF-8">
 <title>Document</title>

 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>

 <script>

    var app = angular.module("myApp",[]);

    function MyCtrlr($scope){
        $scope.vars = {val:"xyz"};
    }


    app.directive("dir2", function($compile){
        return {
            restrict : 'E',
            replace : true,
            compile :function(el, attrs) {
                var newhtml =  '<dir1 field="' + attrs.field + '" />';
                return function(scope, el, attrs) {
                    console.log('dir2 parent = ' + el.parent());
                    el.replaceWith($compile(newhtml)(scope));
                }
            }
        }
    });

    app.directive("dir1", function($compile){
        return {
            restrict : 'E',
            replace : true,
            template: '<input type="text" ng-model="field" />',
            scope: {
                field: '='
            },
            link: function(scope, el, attrs) {
                    console.log('dir1 parent = ' + el.parent());
                    console.log(scope.field);
                }
        }
    });

 </script>

 </head>
 <body>
 <div ng-app="myapp">
     Testing
 <div ng-controller = "MyCtrlr">
     <span ng-bind="vars.val"></span>
     <dir2 field="vars"></dir2>
 </div>
 </div>
 </body>
 </html>
于 2013-09-22T19:10:40.613 回答
0

以下是您如何完成您想做的事情:

工作人员

var app = angular.module('plunker', []);

function MyCtrlr($scope){
    $scope.vars = {val:"xyz"};
}

app.directive("dir2", function($compile){
    return {
        restrict : 'E',
        replace : true,
        template: '<dir1></dir1>',
        link: function(scope, el, attrs) {
        }
    };
});

app.directive("dir1", function($compile){
    return {
        restrict : 'E',
        scope: {
          field: '='
        },
        link: function(scope, el, attrs) {
          scope.model = scope.field;
          el.replaceWith($compile('<input type="text" ng-model="model.val" />')(scope));
        }
    };
});

这保留了双向数据绑定,但在使用上相当有限。我假设您的用例是您的问题的简化,否则不同的方法可能会更简单。

我仍在研究你的小提琴到底出了什么问题的细节,当我弄清楚时会发布一个编辑。

于 2013-09-22T19:27:13.790 回答
0

最后的小提琴

Angularjs 链式指令替换元素

我的目标是开发一个通用指令来使用 Angularjs 为 Activiti 引擎任务呈现表单元素。为此,我开发了一个指令(比如 dir1),它基于表单元素的某些属性将呈现适当类型的 html 元素(输入(文本、复选框)、选择或跨度)替换 dir1 元素。

采集 Activiti 表单的控制器由以下代码模拟

功能 MyCtrlr($scope) {

$scope.v = [{value: 'init0'},

    {value: 'init1'},

    {value: 'init2'},

    {value: 'init3'}

];

$scope.formVals = {

    vals: [{

        id: 'one',

        type: 'string',

        value: 'xyz'

    }, {

        id: 'two',

        type: 'enum',

        value: '2',

        writable:true,

        enumValues: [{

            'id': 1,

            'name': 'ek'

        }, {

            'id': 2,

            'name': 'don'

        }]

    }, {

        id: 'three',

        type: 'enum',

        value: 'abc',

        writable:true,

        enumValues: [{

            'id': 3,

            'name': 'tin'

        }, {

            'id': 4,

            'name': 'chaar'

        }]

    }, {

        id: 'four',

        type: 'enum',

        value: 'abc',

        writable:true,

        enumValues: [{

            'id': 5,

            'name': 'paach'

        }, {

            'id': 6,

            'name': 'sahaa'

        }]

    },

        {id:'five',

            type:'string',

            value:'test',

            writable:true

        }

    ]

};

//$scope.formVals.vals[0].varRef = $scope.v[0];

//$scope.formVals.vals[1].varRef = $scope.v[1];

$scope.formVals.vals[2].varRef = $scope.v[2];

$scope.formVals.vals[3].varRef = $scope.v[3];



$scope.verify = function () {

    alert($scope.v[0].value + '...' + $scope.v[1].value + '...' + $scope.v[2].value + '...' + $scope.v[3].value);

};

}

指令 dir1 如下

app.directive('dir1', function ($compile) {

var getTemplate = function(fld, fvarnm, debug) {

    value = ' value="' + fld.value + '"';

    nm = ' name="' + fld.id + '"';

    ngmodel = ' ng-model="' + fvarnm + '.varRef.value"';

    disabled = fld.writable?'':' disabled=disabled';

    switch(fld.type) {

        case 'activitiUser':

        case 'enum':

            template = '<select '

                + nm + disabled

                + (fld.varRef != null?ngmodel:'');

            template += '<option></option>';

            for (e in fld.enumValues) {

                selected = '';

                ev = fld.enumValues[e];

                if ((fld.varRef == null && (fld.value == ev.id)) || (fld.varRef != null) && (fld.varRef.value == ev.id))

                    selected = ' SELECTED ';

                template += '<option value="' + ev.id + '"' +  selected + '>' + ev.name + '</option>';

            }

            template += '</select>';

            break;

        case 'boolean':

            template = '<input type="checkbox"'

                + nm + disabled

                + (fld.varRef != null?ngmodel:value)

                + (fld.value?' CHECKED':'')

                + '></input>';

            break;

        default:

            template = '<input type="text"'

                + nm + disabled

                + (fld.varRef != null?ngmodel:value)

                + ' value-format="' + fld.type + ' '

                + fld.datePattern + '"'

                + '></input>';

    }

    if (fld.varRef != null && typeof(debug) != 'undefined' && debug.toLowerCase() == 'true') {

        template = '<div>' + template

            + '<span ng-bind="' + fvarnm

            + '.varRef.value"></span>' + '</div>';

    }

    return template;

};



return {

    restrict: 'E',

    replace: true,

    scope : {

        field : '='

    },

    link : function(scope, element, attrs) {
        html = getTemplate(scope.field, attrs.field, attrs.debug);
        element.replaceWith($compile(html)(scope.$parent));
    }    

};

});

然而,当 Activiti 之上的应用程序的细微差别出现时,我决定让开发人员能够使用 dir1 来满足他的一般要求,并允许他开发自己的指令链接到 dir1 来处理这些细微差别。关于细微差别——基于表单元素的属性,应用程序开发人员要么使用 dir1 提供的通用渲染,要么将 dir2 元素替换为适当的 html 元素。

我添加了 dir2 如下 -

app.directive('dir2', function ($compile) {

var getTemplate2 = function(scope, el, attrs) {

    html2 = "<dir1 field='" + attrs.field + "'></dir1>";

    if (scope.field.id == 'five') {

        html2 = '<span style="font-weight:bold" ';

        if (typeof(scope.field.varRef) != 'undefined' && scope.field.varRef) {

            html2 += ' ng-bind="f.varRef.value" ';

        } else {

            html2 += ' ng-bind="f.value" ';

        }

        html2 += '></span> ';

    }

    return html2;

};



return {

    restrict: 'E',

    replace : true,

    scope : {

        field : '='

    },

    link: function (scope, el, attrs) {

         var html2 = getTemplate2(scope, el, attrs);

        el.replaceWith($compile(html2)(scope.$parent));

   }

};

});

但是,我在 dir1 中的 replaceWith 调用中开始出现 null 父级错误。经过大量迷失方向的思考和控制台记录后,我意识到在 el.replaceWith($compile(html2)(scope.$parent)) 语句中编译 html2 的那一刻,只要 html2 是 dir1 元素,就会触发 dir1 链接函数。此时 dir1 元素没有任何 parentNode。因此,我想出了以下安排。在gettemplate2函数中html2的默认值变成了html2="",即传递parent属性。在 dir1 链接函数中,我进行了以下更改 html = getTemplate(scope.field, attrs.field, attrs.debug); scope.dir1el = $compile(html)(scope); if (typeof(attrs.parent) == 'undefined') { element.replaceWith(scope.dir1el); } 从而防止在 dir1 中替换。dir2 的互补变化是

        var html2 = getTemplate2(scope, el, attrs);
        if (html2 == null) {
            $compile("<dir1 parent='true' field='" + attrs.field + "'></dir1>")(scope.$parent);
            ne = scope.$$nextSibling.dir1el;
        } else {
            ne = $compile(html2)(scope.$parent);
        }
        el.replaceWith(ne);

由于 dir1 和 dir2 是同级指令,我必须使用 $$nextSibling 访问 dir1 范围。因此,我可以根据需要将 dir2 中的元素替换为 dir1 或 dir2 生成的元素。

我还使用属性指令 dir3 开发了一个替代解决方案,其中 dir3 将成为 dir1 的属性。这里 dir1 作用域成为 dir3 的父作用域。并且 dir3 中的定制元素是 replaces element 替换由 dir1 创建的元素。因此,该解决方案涉及双 DOM 替换。

于 2013-09-24T07:36:43.493 回答