Como obter atributos avaliados dentro de uma diretiva personalizada

363

Estou tentando obter um atributo avaliado da minha diretiva personalizada, mas não consigo encontrar a maneira certa de fazê-lo.

Eu criei este jsFiddle para elaborar.

<div ng-controller="MyCtrl">
    <input my-directive value="123">
    <input my-directive value="{{1+1}}">
</div>

myApp.directive('myDirective', function () {
    return function (scope, element, attr) {
        element.val("value = "+attr.value);
    }
});

o que estou perdendo?

Shlomi Schwartz
fonte
Você pode seguir o link abaixo para entender melhor as diretrizes. undefinednull.com/2014/02/11/...
Prasanna Sasne

Respostas:

573

Aviso: atualizo esta resposta à medida que encontro melhores soluções. Também mantenho as respostas antigas para referência futura, desde que permaneçam relacionadas. A melhor e mais recente resposta vem em primeiro lugar.

Melhor resposta:

As diretivas nos angularjs são muito poderosas, mas leva tempo para compreender quais processos estão por trás delas.

Ao criar diretivas, o angularjs permite criar um escopo isolado com algumas ligações ao escopo pai. Essas ligações são especificadas pelo atributo no qual você anexa o elemento no DOM e como você define a propriedade do escopo no objeto de definição de diretiva .

Existem 3 tipos de opções de ligação que você pode definir no escopo e gravá-las como atributo relacionado a prefixos.

angular.module("myApp", []).directive("myDirective", function () {
    return {
        restrict: "A",
        scope: {
            text: "@myText",
            twoWayBind: "=myTwoWayBind",
            oneWayBind: "&myOneWayBind"
        }
    };
}).controller("myController", function ($scope) {
    $scope.foo = {name: "Umur"};
    $scope.bar = "qwe";
});

HTML

<div ng-controller="myController">
    <div my-directive my-text="hello {{ bar }}" my-two-way-bind="foo" my-one-way-bind="bar">
    </div>
</div>

Nesse caso, no escopo da diretiva (seja na função de vinculação ou no controlador), podemos acessar essas propriedades assim:

/* Directive scope */

in: $scope.text
out: "hello qwe"
// this would automatically update the changes of value in digest
// this is always string as dom attributes values are always strings

in: $scope.twoWayBind
out: {name:"Umur"}
// this would automatically update the changes of value in digest
// changes in this will be reflected in parent scope

// in directive's scope
in: $scope.twoWayBind.name = "John"

//in parent scope
in: $scope.foo.name
out: "John"


in: $scope.oneWayBind() // notice the function call, this binding is read only
out: "qwe"
// any changes here will not reflect in parent, as this only a getter .

Resposta "Ainda OK":

Como essa resposta foi aceita, mas tem alguns problemas, vou atualizá-la para uma melhor. Aparentemente, $parseé um serviço que não se encontra nas propriedades do escopo atual, o que significa que apenas utiliza expressões angulares e não pode alcançar o escopo. {{, }}expressões são compiladas durante o início do angularjs, o que significa que, quando tentamos acessá-las em nosso postlinkmétodo de diretivas , elas já são compiladas. ( já {{1+1}}está 2na diretiva).

É assim que você deseja usar:

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

myApp.directive('myDirective', function ($parse) {
    return function (scope, element, attr) {
        element.val("value=" + $parse(attr.myDirective)(scope));
    };
});

function MyCtrl($scope) {
    $scope.aaa = 3432;
}​

.

<div ng-controller="MyCtrl">
    <input my-directive="123">
    <input my-directive="1+1">
    <input my-directive="'1+1'">
    <input my-directive="aaa">
</div>​​​​​​​​

Uma coisa que você deve notar aqui é que, se quiser definir a cadeia de valor, coloque-a entre aspas. (Veja a terceira entrada)

Aqui está o violino para brincar: http://jsfiddle.net/neuTA/6/

Resposta antiga:

Não estou removendo isso para pessoas que podem ser enganadas como eu; observe que o uso $evalé perfeitamente adequado da maneira correta, mas $parsetem um comportamento diferente; provavelmente você não precisará disso na maioria dos casos.

A maneira de fazer isso é, mais uma vez, usando scope.$eval. Além de compilar a expressão angular, também tem acesso às propriedades do escopo atual.

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

myApp.directive('myDirective', function () {
    return function (scope, element, attr) {
        element.val("value = "+ scope.$eval(attr.value));
    }
});

function MyCtrl($scope) {

}​

O que você está perdendo era $eval.

http://docs.angularjs.org/api/ng.$rootScope.Scope#$eval

Executa a expressão no escopo atual retornando o resultado. Quaisquer exceções na expressão são propagadas (não capturadas). Isso é útil ao avaliar expressões angulares.

Umur Kontacı
fonte
Obrigado pela resposta, no entanto, esta não é a solução. Atualizei o violino com seu código. jsfiddle.net/neuTA/3
Shlomi Schwartz
No Chrome, recebo esse erro ao tentar usar o escopo. $ Parse: O objeto # <Object> não tem o método '$ parse'. Se eu injetar o serviço $ parse - function ($ parse) {função de retorno (escopo ... -, tente: "value =" + $ parse (attr.value) - isso não parece funcionar para mim tampouco.
Mark Rajcok
@ Mark você está certo, estranho ele funciona no exemplo do violino ( jsfiddle.net/neuTA/4 ), mas não no código que eu tenho ... versões angulares?
Shlomi Schwartz
2
Na seção "Melhor resposta", $scope.textserá indefinido na função de vinculação. A maneira como a resposta está atualmente redigida parece que não seria indefinida. Você precisa usar $ observe () (ou $ watch () também funcionará aqui também) para ver de forma assíncrona o valor interpolado. Veja a minha resposta e também stackoverflow.com/questions/14876112/… #
27413 Mark Rajcok
11
Na resposta "Ainda OK" , parece que o $parseserviço foi injetado e nunca usado. Estou esquecendo de algo?
Superjo
83

Para um valor de atributo que precisa ser interpolado em uma diretiva que não esteja usando um escopo isolado, por exemplo,

<input my-directive value="{{1+1}}">

use o método Attributes ' $observe:

myApp.directive('myDirective', function () {
  return function (scope, element, attr) {
    attr.$observe('value', function(actual_value) {
      element.val("value = "+ actual_value);
    })
 }
});

Na página da diretiva ,

observação de atributos interpolados: use $observepara observar as alterações de valor dos atributos que contêm interpolação (por exemplo src="{{bar}}"). Isso não só é muito eficiente, mas também é a única maneira de obter facilmente o valor real, porque durante a fase de vinculação a interpolação ainda não foi avaliada e, portanto, o valor está definido no momento undefined.

Se o valor do atributo for apenas uma constante, por exemplo,

<input my-directive value="123">

você pode usar $ eval se o valor for um número ou booleano e desejar o tipo correto:

return function (scope, element, attr) {
   var number = scope.$eval(attr.value);
   console.log(number, number + 1);
});

Se o valor do atributo for uma constante de sequência, ou você desejar que o valor seja do tipo sequência na sua diretiva, é possível acessá-lo diretamente:

return function (scope, element, attr) {
   var str = attr.value;
   console.log(str, str + " more");
});

No seu caso, no entanto, como você deseja suportar valores e constantes interpolados, use $observe.

Mark Rajcok
fonte
Essa foi a única solução que você encontrou?
Shlomi Schwartz
4
Sim, e como a página de diretiva recomenda essa abordagem, é assim que eu faria.
Mark Rajcok
7
+1, esta é a melhor resposta IMO, uma vez que não força um escopo sobre a directiva e também cobre as mudanças de atributos com $ observar
BiAiB
4

As outras respostas aqui são muito corretas e valiosas. Mas, às vezes, você quer apenas simples: obter um valor antigo e analisado na instanciação da diretiva, sem precisar de atualizações e sem mexer no escopo isolado. Por exemplo, pode ser útil fornecer uma carga declarativa em sua diretiva como uma matriz ou objeto hash no formato:

my-directive-name="['string1', 'string2']"

Nesse caso, você pode ir direto ao ponto e usar apenas um bom básico angular.$eval(attr.attrName).

element.val("value = "+angular.$eval(attr.value));

Violino de trabalho .

XML
fonte
Não sei se você usou uma versão angular antiga ou não, mas todos os seus exemplos de código são javascript inválido (my-Directive-name =) ou angular inválido (angular. $ Eval não existe), portanto, -1
BiAiB
Ummm ... dado que este post tem mais de um ano, não seria de todo surpreendente se algo estivesse obsoleto. No entanto, uma pesquisa de 10 segundos no Google encontrará muito material em $ eval, incluindo aqui na SO . E o outro exemplo que você cita é uma invocação em HTML, não em Javascript.
XML
$ scope. $ eval (attr.val) funciona em angular 1.4. Requer que $ scope seja injetado na função de link de diretiva.
Martin Connell
4

Para a mesma solução que eu estava procurando Angularjs directive with ng-Model.
Aqui está o código que resolve o problema.

    myApp.directive('zipcodeformatter', function () {
    return {
        restrict: 'A', // only activate on element attribute
        require: '?ngModel', // get a hold of NgModelController
        link: function (scope, element, attrs, ngModel) {

            scope.$watch(attrs.ngModel, function (v) {
                if (v) {
                    console.log('value changed, new value is: ' + v + ' ' + v.length);
                    if (v.length > 5) {
                        var newzip = v.replace("-", '');
                        var str = newzip.substring(0, 5) + '-' + newzip.substring(5, newzip.length);
                        element.val(str);

                    } else {
                        element.val(v);
                    }

                }

            });

        }
    };
});


DOM HTML

<input maxlength="10" zipcodeformatter onkeypress="return isNumberKey(event)" placeholder="Zipcode" type="text" ng-readonly="!checked" name="zipcode" id="postal_code" class="form-control input-sm" ng-model="patient.shippingZipcode" required ng-required="true">


Meu resultado é:

92108-2223
Satish Singh
fonte
2
var myApp = angular.module('myApp',[]);

myApp .directive('myDirective', function ($timeout) {
    return function (scope, element, attr) {
        $timeout(function(){
            element.val("value = "+attr.value);
        });

    }
});

function MyCtrl($scope) {

}

Use $ timeout porque a diretiva chama após o carregamento do DOM, para que suas alterações não se apliquem

user1693371
fonte