Comment obtenir des attributs évalués dans une directive personnalisée

363

J'essaie d'obtenir un attribut évalué de ma directive personnalisée, mais je ne trouve pas la bonne façon de le faire.

J'ai créé ce jsFiddle pour élaborer.

<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'est-ce que je rate?

Shlomi Schwartz
la source
Vous pouvez suivre le lien ci-dessous pour une meilleure compréhension des directives. undefinednull.com/2014/02/11/…
Prasanna Sasne

Réponses:

573

Remarque: je mets à jour cette réponse car je trouve de meilleures solutions. Je garde également les anciennes réponses pour référence future tant qu'elles restent liées. La dernière et la meilleure réponse vient en premier.

Meilleure réponse:

Les directives dans angularjs sont très puissantes, mais il faut du temps pour comprendre quels processus se cachent derrière elles.

Lors de la création de directives, angularjs vous permet de créer une portée isolée avec quelques liaisons à la portée parent. Ces liaisons sont spécifiées par l' attribut auquel vous attachez l'élément dans DOM et la façon dont vous définissez la propriété scope dans l' objet de définition de directive .

Il existe 3 types d'options de liaison que vous pouvez définir dans la portée et que vous écrivez en tant qu'attribut lié aux préfixes.

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>

Dans ce cas, dans le champ d'application de la directive (que ce soit dans la fonction de liaison ou le contrôleur), nous pouvons accéder à ces propriétés comme ceci:

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

"Toujours OK" Réponse:

Depuis que cette réponse a été acceptée, mais a quelques problèmes, je vais la mettre à jour pour une meilleure. Apparemment, $parsec'est un service qui ne réside pas dans les propriétés de la portée actuelle, ce qui signifie qu'il ne prend que des expressions angulaires et ne peut pas atteindre la portée. {{, les }}expressions sont compilées lors de l'initiation de angularjs, ce qui signifie que lorsque nous essayons d'y accéder dans notre postlinkméthode de directives , elles sont déjà compilées. ( {{1+1}}est 2déjà dans la directive).

Voici comment vous souhaitez utiliser:

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>​​​​​​​​

Une chose que vous devez noter ici est que, si vous souhaitez définir la chaîne de valeur, vous devez la mettre entre guillemets. (Voir 3e entrée)

Voici le violon avec lequel jouer: http://jsfiddle.net/neuTA/6/

Ancienne réponse:

Je ne supprime pas cela pour les gens qui peuvent être induits en erreur comme moi, notez que l'utilisation $evalest parfaitement bien la bonne façon de le faire, mais $parsea un comportement différent, vous n'aurez probablement pas besoin de cela pour utiliser dans la plupart des cas.

La façon de le faire est, encore une fois, d'utiliser scope.$eval. Non seulement il compile l'expression angulaire, mais il a également accès aux propriétés de la portée actuelle.

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

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

function MyCtrl($scope) {

}​

Ce qui vous manquait était $eval.

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

Exécute l'expression sur la portée actuelle renvoyant le résultat. Toutes les exceptions dans l'expression sont propagées (non capturées). Ceci est utile lors de l'évaluation des expressions angulaires.

Umur Kontacı
la source
Merci pour la réponse, mais ce n'est pas la solution. J'ai mis à jour le violon avec votre code. jsfiddle.net/neuTA/3
Shlomi Schwartz
Dans Chrome, j'obtiens cette erreur lorsque j'essaie d'utiliser la portée. $ Parse: l'objet # <Object> n'a pas de méthode '$ parse'. Si j'injecte le service $ parse - fonction ($ parse) {fonction de retour (portée ... - alors essayez: "valeur =" + $ parse (attr.value) - cela ne semble pas fonctionner pour moi soit.
Mark Rajcok
@Mark vous avez raison, étrange cela fonctionne dans l'exemple du violon ( jsfiddle.net/neuTA/4 ) mais pas dans le code que j'ai ... versions angulaires?
Shlomi Schwartz
2
Dans la section "Meilleure réponse", $scope.textne sera pas défini dans la fonction de liaison. D'après la formulation actuelle de la réponse, il semble qu'elle ne serait pas indéfinie. Vous devez utiliser $ observ () (ou $ watch () fonctionnera également ici) pour voir de manière asynchrone la valeur interpolée. Voir ma réponse et aussi stackoverflow.com/questions/14876112/…
Mark Rajcok
1
Dans la réponse "Toujours OK", il semble que le $parseservice est injecté puis jamais utilisé. Suis-je en train de manquer quelque chose?
superjos
83

Pour une valeur d'attribut qui doit être interpolée dans une directive qui n'utilise pas une portée isolée, par exemple,

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

utiliser la méthode des attributs $observe:

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

Depuis la page des directives ,

observation des attributs interpolés: permet $observed'observer les changements de valeur des attributs qui contiennent une interpolation (par exemple src="{{bar}}"). Non seulement cela est très efficace, mais c'est également le seul moyen d'obtenir facilement la valeur réelle car pendant la phase de liaison, l'interpolation n'a pas encore été évaluée et la valeur est alors définie sur undefined.

Si la valeur d'attribut est juste une constante, par exemple,

<input my-directive value="123">

vous pouvez utiliser $ eval si la valeur est un nombre ou un booléen, et vous voulez le type correct:

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

Si la valeur d'attribut est une constante de chaîne ou si vous souhaitez que la valeur soit de type chaîne dans votre directive, vous pouvez y accéder directement:

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

Dans votre cas, cependant, puisque vous souhaitez prendre en charge les valeurs et les constantes interpolées, utilisez $observe.

Mark Rajcok
la source
était-ce la seule solution que vous ayez trouvée?
Shlomi Schwartz
4
Oui, et puisque la page directive recommande cette approche, voici comment je le ferais.
Mark Rajcok
7
+1, c'est la meilleure réponse IMO car elle ne force pas la portée de la directive et couvre également les changements d'attribut avec $ observe
BiAiB
4

Les autres réponses ici sont très correctes et précieuses. Mais parfois, vous voulez simplement: obtenir une ancienne valeur analysée simple lors de l'instanciation de la directive, sans avoir besoin de mises à jour et sans gâcher la portée de l'isolat. Par exemple, il peut être utile de fournir une charge utile déclarative dans votre directive sous forme de tableau ou d'objet de hachage sous la forme:

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

Dans ce cas, vous pouvez aller droit au but et utiliser simplement un joli basique angular.$eval(attr.attrName).

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

Violon de travail .

XML
la source
Je ne sais pas si vous avez utilisé une ancienne version angulaire ou quoi, mais tous vos exemples de code sont soit Javascript non valide (my-directive-name =) ou angulaire invalide (angulaire. $ Eval n'existe pas), donc -1
BiAiB
Ummm ... étant donné que ce message a plus d'un an, il ne serait pas du tout surprenant que quelque chose soit obsolète depuis. Cependant, une recherche Google de 10 secondes vous trouverait beaucoup de matériel sur $ eval, y compris ici à SO . Et l'autre exemple que vous citez est une invocation en HTML, pas en Javascript.
XML
$ scope. $ eval (attr.val) fonctionne en angulaire 1.4. Nécessite que $ scope soit injecté dans la fonction de lien de directive.
Martin Connell
4

Pour la même solution que je cherchais Angularjs directive with ng-Model.
Voici le code qui résout le problème.

    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">


Mon résultat est:

92108-2223
Satish Singh
la source
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) {

}

Utilisez $ timeout car la directive appelle après le chargement de dom pour que vos modifications ne s'appliquent pas

user1693371
la source