2013-09-22 4 views
2

Моя цель - Директива dir2 заменяет собой директиву dir1, которая, в свою очередь, заменяется на вход.Цепь директивы Angularjs

Однако при замене dir1 на входе я получаю родительское исключение null в функции replaceWith.

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

ответ

0

Final Fiddle

Angularjs Цепные Директивы Замена элементов

Я начал с целью разработать общую директиву для визуализации формы элементов для Activiti задач двигателей, использующих Angularjs. Для чего я разработал директиву (скажем, dir1), которая на основе определенных свойств элемента формы предоставила соответствующий тип html-элемента (ввод (текст, флажок), выбор или интервал), заменяющий элемент dir1.

Контроллер, который собирает Activiti форму имитируется следующим кодом

функция MyCtrlr ($ охват) {

$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 следующим

приложение ,директива ('dir1', функция ($ компиляции) {

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', функция ($ компиляции) {

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

    } 

}; 

});

Однако я начал получать нулевую родительскую ошибку в replaceWith call in dir1. После много дезориентированного мышления и консольного ведения журнала я понял, что момент html2 собирался в команде el.replaceWith ($ compile (html2) (scope. $ Parent)), функция dir1 link запускалась всякий раз, когда html2 был элементом dir1. В этот момент элемент dir1 не имел никакого родительскогоNode. Поэтому я придумал следующее соглашение. В функции gettemplate2 значение html2 по умолчанию стало html2 = "", т. Е. Передало родительский атрибут. В функции dir1 link я сделал следующие изменения: 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 являются двойниками директивы, я должны были получить доступ к dir1 сфере, используя $$ NextSibling. Таким образом, я могу заменить элемент в dir2 на один, сгенерированный dir1 или dir2, если это необходимо.

Я также разработал альтернативное решение, используя директиву атрибута dir3, где dir3 станет атрибутом dir1. Здесь область dir1 становится родительской областью dir3. И сделанный на заказ элемент в dir3 заменяет элемент, заменяющий элемент, созданный dir1. Таким образом, это решение включает двойную замену DOM.

1

В основном вы получаете сообщение об ошибке, потому что процесс компиляции происходит в два этапа: compile и link. Поскольку ваши директивы скомпилируются в одно и то же время (1-я фаза), когда dir2 заканчивает свою компиляцию, элемент DOM dir1 еще не готов для манипуляций.

Так что я изменил dir1, чтобы использовать фазу связи процесса (2-я фаза).

Как это 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> 
+0

Когда я пытаюсь использовать ваш плункер, он не работает, входной текст имеет [объект Object] в нем, и двусторонняя привязка данных нарушена. – mortalapeman

+0

Кроме того, обе директивы все еще используют фазу связи. Когда вы возвращаете функцию из функции компиляции, это функция postLink и будет выполняться во время фазы связывания. – mortalapeman

+0

Hummm ... Я заметил это, но я не хотел менять код! Я думал, что вы действительно хотели, чтобы объект [object Object] во входном tex :) Это быстрое решение. просто добавьте '.val' к переменной. здесь это обновленный plunk (http://plnkr.co/edit/GrOPkNaxOxcXFDZfDwWh). Двухстороннее связывание данных работает должным образом. –

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

Это сохраняет двустороннее связывание данных, но довольно ограничено в его использовании. Я предполагаю, что ваш вариант использования - это упрощение вашей проблемы, иначе другой подход может быть проще.

Я все еще разрабатываю детали о том, что происходит неправильно в вашей скрипке, опубликует редактирование, когда я это выясню.

Смежные вопросы