Posso usar o modelo ng com escopo isolado?

Estou criando uma diretiva simples ui-datetime. Divide o object Date do javascript em _date, _hours e _minutes parts. _date usa jquery ui datepicker, _hours e _minutes – inputs numéricas.

angular.module("ExperimentsModule", []) .directive("uiDatetime", function () { return { restrict: 'EA', replace: true, template: '
' + '' + '' + '' + '
Child datetime1: {{datetime1}}' + '
', require: 'ngModel', scope: true, link: function (scope, element, attrs, ngModelCtrl) { var elDate = element.find('input.date'); ngModelCtrl.$render = function () { var date = new Date(ngModelCtrl.$viewValue); var fillNull = function (num) { if (num < 10) return '0' + num; return num; }; scope._date = fillNull(date.getDate()) + '.' + fillNull(date.getMonth() + 1) + '.' + date.getFullYear(); scope._hours = date.getHours(); scope._minutes = date.getMinutes(); }; elDate.datepicker({ dateFormat: 'dd.mm.yy', onSelect: function (value, picker) { scope._date = value; scope.$apply(); } }); var watchExpr = function () { var res = scope.$eval('_date').split('.'); if (res.length == 3) return new Date(res[2], res[1] - 1, res[0], scope.$eval('_hours'), scope.$eval('_minutes')); return 0; }; scope.$watch(watchExpr, function (newValue) { ngModelCtrl.$setViewValue(newValue); }, true); } }; }); function TestController($scope) { $scope.datetime1 = new Date(); }

jsfiddle

No github: https://github.com/andreev-artem/angular_experiments/tree/master/ui-datetime

Tanto quanto eu entendo – melhor prática quando você cria um novo componente é usar o escopo isolado.

Quando tentei usar o escopo isolado – nada funciona. ngModel. $ viewValue === indefinido.

Quando eu tentei usar o novo escopo (meu exemplo, não tão boa variante imho) – ngModel usa valor no escopo recém-criado.

Claro que posso criar diretiva com escopo isolado e trabalhar com valor ngModel através de “= expressão” ( exemplo ). Mas acho que trabalhar com ngModelController é uma prática melhor.

Minhas perguntas:

  1. Posso usar ngModelController com escopo isolado?
  2. Se não for possível qual solução é melhor para criar esse componente?

Substituindo scope: true com scope: { datetime1: '=ngModel'} em seu primeiro violino parece funcionar bem – violino . Infelizmente, o link para o violino “exemplo” está quebrado, então não tenho certeza do que você tentou fazer.

Então, parece que ngModelController pode ser usado com um escopo isolado.

Aqui está um violino menor que usa ng-model no HTML / view, um scope isolado e $ setViewValue na function de link: fiddle .

Update : Acabei de descobrir algo bastante interessante: se a propriedade scope for dada um nome diferente – por exemplo, digamos dt1 ao invés de datetime1 – scope: { dt1: '=ngModel'} – não funciona mais! Eu estou supondo que, quando require: 'ngModel' , o ngModelController usa o nome no HTML / view (ou seja, o valor do atributo ng-model) para criar uma propriedade no escopo isolate. Então, se nós especificarmos o mesmo nome no hash do object, tudo estará bem. Mas, se especificarmos um nome diferente, essa nova propriedade de escopo (por exemplo, dt1) não está associada ao ngModelController que solicitamos.

Aqui está um violino atualizado .

Faça sua diretiva executar em uma prioridade mais alta que ngModel e corrija a binding de modelo para seu escopo isolado. Eu escolhi uma prioridade de ‘100’ que é o mesmo nível da diretiva de input, após manipulações de modelo de alta prioridade como ngRepeat, mas antes do padrão de 0, que é o que ngModel usa.

Aqui está o código de exemplo:

 myDirective = function() { return { compile: function(tElement, tAttrs, transclude) { // Correct ngModel for isolate scope if (tAttrs.ngModel) { tAttrs.$set('model', tAttrs.ngModel, false); tAttrs.$set('ngModel', 'model', false); } return { post: function(scope, iElement, iAttrs, controller) { // Optionally hook up formatters and parsers controller.$formatters.push(function(value) { // ... }) // Render return controller.$render = function() { if (!controller.$viewValue) { return; } angular.extend(scope, controller.$viewValue); }; } }; }, priority: 100, require: '^ngModel', scope: { model: '=' }, }; } 

Durante a compilation, a diretiva verifica se o atributo ngModel está presente. Essa verificação funciona no valor normalizado usando Atributos do Angular. Se o atributo estiver presente, ele será substituído por ‘model’ (não ‘ngModel’), que é o nome ligado a dados em nosso isolado. No entanto, também precisamos criar um atributo para que o Angular possa realizar a vinculação de dados para nós. Ambos os atributos podem ser (a seu critério) modificados com um parâmetro false que deixa o DOM inalterado.

Eu acho que tive o mesmo problema, e encontrei uma solução parcial ainda utilizável.

Então, o problema tem várias partes:

  1. sua diretiva personalizada quer algumas propriedades particulares, ou seja, escopo isolado
  2. O nó DOM pode ter apenas um escopo, todas as diretivas o compartilham
  3. ngModel = “alguma coisa” se liga a “alguma coisa” nesse escopo compartilhado (isolado), e esse é o problema real

Então, meu primeiro passo foi rewrite minha diretiva para usar scope:true vez de scope:{...} (na verdade, isso era um requisito, porque eu queria usar algumas propriedades de escopo global dentro do conteúdo transcluído da minha diretiva): coisas como attrs.$observe() , $scope.$parent.$watch() , etc. ajudaram.

Então, em compile() eu re-limite o ngModel para a propriedade do escopo pai: attrs.$set('ngModel', '$parent.' + attrs.ngModel, false) . E isso é tudo.

Aqui está minha diretiva, com o código não essencial removido:

 angular.module('App', []).directive('dir', function () { return { /* This one is important: */ scope:true, compile:function (element, attrs, transclude) { /* The trick is here: */ if (attrs.ngModel) { attrs.$set('ngModel', '$parent.' + attrs.ngModel, false); } return function ($scope, element, attrs, ngModel) { // link function body }; } }; }); 

Experimente uma versão disso:

 .directive('myDir', function() { return { restrict: 'EA', scope: { YYY: '=ngModel' }, require: 'ngModel', replace: true, template: function render(element, attrs) { var type = attrs.type || 'text'; var required = attrs.hasOwnProperty('required') ? " required='required'" : ""; return "'; } }; });