Angularjs指令链接

时间:2013-09-22 15:10:04

标签: angularjs-directive

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

但是在输入dir1替换期间,我在replaceWith函数中获取parent是null异常。

Fiddle for the same

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

3 个答案:

答案 0 :(得分:1)

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

所以我改变dir1以使用流程的链接阶段(第2阶段)。

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

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>

答案 1 :(得分:0)

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

Wokring plunker

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

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

我仍然在研究你的小提琴出了什么问题的细节,当我想出来的时候会发布一个编辑。

答案 2 :(得分:0)

Final Fiddle

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提供的泛型渲染,也可以使用适当的html元素替换dir2元素。

我添加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父错误。在经过多次迷失方向思考和控制台日志记录之后,我意识到当html2被编译为el.replaceWith($ compile(html2)(范围。$ parent))语句时,只要html2是dir1元素,dir1链接函数就会触发。此时dir1元素没有任何parentNode。 因此我想出了以下安排。 在gettemplate2函数中,html2默认值变为html2 =“”,即传递父属性。 在dir1链接功能中,我做了以下更改             html = getTemplate(scope.field,attrs.field,attrs.debug);             scope.dir1el = $ compile(html)(范围);             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中的定制元素替换了dir1创建的元素替换元素。因此,该解决方案涉及双DOM替换。