Cómo obtener atributos evaluados dentro de una directiva personalizada

363

Estoy tratando de obtener un atributo evaluado de mi directiva personalizada, pero no puedo encontrar la forma correcta de hacerlo.

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

¿Qué me estoy perdiendo?

Shlomi Schwartz
fuente
Puede seguir el siguiente enlace para comprender mejor las directivas. undefinednull.com/2014/02/11/…
Prasanna Sasne

Respuestas:

573

Aviso: actualizo esta respuesta a medida que encuentro mejores soluciones. También mantengo las viejas respuestas para referencia futura siempre que sigan relacionadas. La última y mejor respuesta es lo primero.

Mejor respuesta

Las directivas en angularjs son muy poderosas, pero lleva tiempo comprender qué procesos hay detrás de ellas.

Al crear directivas, angularjs le permite crear un ámbito aislado con algunos enlaces al ámbito primario. Estos enlaces están especificados por el atributo que adjunta el elemento en DOM y cómo define la propiedad del alcance en el objeto de definición de directiva .

Hay 3 tipos de opciones de enlace que puede definir en alcance y las escribe como atributo relacionado con prefijos.

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>

En ese caso, en el ámbito de la directiva (ya sea en función de enlace o controlador), podemos acceder a estas propiedades de esta manera:

/* 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 .

"Aún está bien" Respuesta:

Dado que esta respuesta fue aceptada, pero tiene algunos problemas, la actualizaré a una mejor. Aparentemente, $parsees un servicio que no reside en las propiedades del alcance actual, lo que significa que solo toma expresiones angulares y no puede alcanzar el alcance. {{, las }}expresiones se compilan mientras se inicia angularjs, lo que significa que cuando intentamos acceder a ellas en nuestro postlinkmétodo de directivas , ya están compiladas. (ya {{1+1}}está 2en la directiva).

Así es como te gustaría 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>​​​​​​​​

Una cosa que debe notar aquí es que, si desea establecer la cadena de valor, debe ajustarla entre comillas. (Ver tercera entrada)

Aquí está el violín para jugar: http://jsfiddle.net/neuTA/6/

Vieja respuesta:

No estoy eliminando esto para las personas que pueden ser engañadas como yo, tenga en cuenta que usar $evalestá perfectamente bien la forma correcta de hacerlo, pero $parsetiene un comportamiento diferente, probablemente no necesite usarlo en la mayoría de los casos.

La forma de hacerlo es, una vez más, usando scope.$eval. No solo compila la expresión angular, sino que también tiene acceso a las propiedades del ámbito actual.

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

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

function MyCtrl($scope) {

}​

Lo que te estás perdiendo fue $eval.

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

Ejecuta la expresión en el ámbito actual que devuelve el resultado. Cualquier excepción en la expresión se propaga (sin capturar). Esto es útil al evaluar expresiones angulares.

Umur Kontacı
fuente
Gracias por la respuesta, sin embargo, esta no es la solución. He actualizado el violín con tu código. jsfiddle.net/neuTA/3
Shlomi Schwartz
En Chrome recibo este error cuando intento usar el alcance. $ Parse: Object # <Object> no tiene el método '$ parse'. Si inyecto el servicio $ parse - function ($ parse) {return function (scope ... - entonces intente: "value =" + $ parse (attr.value) - eso no parece funcionar para mí tampoco.
Mark Rajcok
@ Mark tienes razón, extraño, funciona en el ejemplo de violín ( jsfiddle.net/neuTA/4 ) pero no en el código que tengo ... ¿versiones angulares?
Shlomi Schwartz
2
En la sección "Mejor respuesta", $scope.textestará indefinido en la función de vinculación. La forma en que la respuesta está redactada actualmente, parece que no estaría indefinida. Debe usar $ observe () (o $ watch () en realidad también funcionará aquí) para ver asincrónicamente el valor interpolado. Vea mi respuesta y también stackoverflow.com/questions/14876112/…
Mark Rajcok
1
En "Aún está bien" , parece que el $parseservicio se inyecta y luego nunca se usa. ¿Me estoy perdiendo de algo?
superjos
83

Para un valor de atributo que necesita ser interpolado en una directiva que no utiliza un alcance aislado, por ejemplo,

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

use el método de los atributos $observe:

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

Desde la página de la directiva ,

Observación de atributos interpolados: se utiliza $observepara observar los cambios de valor de los atributos que contienen interpolación (p src="{{bar}}". ej .). Esto no solo es muy eficiente, sino que también es la única forma de obtener fácilmente el valor real porque durante la fase de enlace la interpolación aún no se ha evaluado y, por lo tanto, el valor está configurado en este momento undefined.

Si el valor del atributo es solo una constante, por ejemplo,

<input my-directive value="123">

puede usar $ eval si el valor es un número o booleano, y desea el tipo correcto:

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

Si el valor del atributo es una constante de cadena, o si desea que el valor sea de tipo cadena en su directiva, puede acceder a él directamente:

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

Sin embargo, en su caso, dado que desea admitir valores y constantes interpolados, use $observe.

Mark Rajcok
fuente
¿Fue esta la única solución que encontraste?
Shlomi Schwartz
44
Sí, y dado que la página de la directiva recomienda este enfoque, así es como lo haría.
Mark Rajcok
77
+1, esta es la mejor respuesta de la OMI, ya que no
impone
4

Las otras respuestas aquí son muy correctas y valiosas. Pero a veces solo quieres lo simple: obtener un valor analizado antiguo simple en la instanciación de directivas, sin necesidad de actualizaciones y sin alterar el alcance de aislamiento. Por ejemplo, puede ser útil proporcionar una carga útil declarativa en su directiva como una matriz o un objeto hash en la forma:

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

En ese caso, puede ir al grano y simplemente usar un buen básico angular.$eval(attr.attrName).

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

Fiddle de trabajo .

XML
fuente
No sé si usó una versión angular anterior o no, pero todos sus ejemplos de código son JavaScript no válido (my-directive-name =) o angular no válido (angular. $ Eval no existe), entonces -1
BiAiB
Ummm ... dado que esta publicación tiene más de un año, no sería sorprendente si algo hubiera quedado en desuso. Sin embargo, una búsqueda en Google de 10 segundos le encontraría mucho material sobre $ eval, incluido aquí mismo en SO . Y el otro ejemplo que cita es una invocación en HTML, no en Javascript.
XML
$ scope. $ eval (attr.val) funciona en angular 1.4. Requiere que $ scope se inyecte en la función de enlace de directiva.
Martin Connell
4

Por la misma solución que estaba buscando Angularjs directive with ng-Model.
Aquí está el código que resuelve el 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">


Mi resultado es:

92108-2223
Satish Singh
fuente
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 la directiva llama después de dom load para que sus cambios no se apliquen

usuario1693371
fuente