AngularJS - Créer une directive qui utilise ng-model


294

J'essaie de créer une directive qui créerait un champ d'entrée avec le même modèle ng que l'élément qui crée la directive.

Voici ce que j'ai trouvé jusqu'à présent:

HTML

<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script>document.write("<base href=\"" + document.location + "\" />");</script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive ng-model="name"></my-directive>
</body>
</html>

Javascript

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

app.controller('MainCtrl', function($scope) {
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div class="some"><label for="{{id}}">{{label}}</label>' +
      '<input id="{{id}}" ng-model="value"></div>',
    replace: true,
    require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      $scope.label = attr.ngModel;
      $scope.id = attr.ngModel;
      console.debug(attr.ngModel);
      console.debug($scope.$parent.$eval(attr.ngModel));
      var textField = $('input', elem).
        attr('ng-model', attr.ngModel).
        val($scope.$parent.$eval(attr.ngModel));

      $compile(textField)($scope.$parent);
    }
  };
});

Cependant, je ne suis pas sûr que ce soit la bonne façon de gérer ce scénario, et il y a un bogue selon lequel mon contrôle n'est pas initialisé avec la valeur du champ cible ng-model.

Voici un Plunker du code ci-dessus: http://plnkr.co/edit/IvrDbJ

Quelle est la bonne façon de gérer cela?

EDIT : Après avoir supprimé le ng-model="value"du modèle, cela semble fonctionner correctement. Cependant, je garderai cette question ouverte parce que je veux vérifier que c'est la bonne façon de procéder.


1
Et si vous le supprimez scopeet le définissez sur scope: false? Comment se lier ng-modeldans ce cas?
Saeed Neamati

Réponses:


210

EDIT : Cette réponse est ancienne et probablement obsolète. Juste un avertissement pour ne pas induire les gens en erreur. Je n'utilise plus Angular donc je ne suis pas en bonne position pour apporter des améliorations.


C'est en fait une assez bonne logique mais vous pouvez simplifier un peu les choses.

Directif

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

app.controller('MainCtrl', function($scope) {
  $scope.model = { name: 'World' };
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'AE', //attribute or element
    scope: {
      myDirectiveVar: '=',
     //bindAttr: '='
    },
    template: '<div class="some">' +
      '<input ng-model="myDirectiveVar"></div>',
    replace: true,
    //require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      console.debug($scope);
      //var textField = $('input', elem).attr('ng-model', 'myDirectiveVar');
      // $compile(textField)($scope.$parent);
    }
  };
});

HTML avec directive

<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive my-directive-var="name"></my-directive>
</body>

CSS

.some {
  border: 1px solid #cacaca;
  padding: 10px;
}

Vous pouvez le voir en action avec ce Plunker .

Voici ce que je vois:

  • Je comprends pourquoi vous voulez utiliser 'ng-model' mais dans votre cas ce n'est pas nécessaire. ng-model consiste à lier les éléments html existants avec une valeur dans la portée. Puisque vous créez vous-même une directive, vous créez un «nouvel» élément html, vous n'avez donc pas besoin de ng-model.

EDIT Comme mentionné par Mark dans son commentaire, il n'y a aucune raison pour que vous ne puissiez pas utiliser ng-model, juste pour respecter la convention.

  • En créant explicitement une portée dans votre directive (une portée `` isolée ''), la portée de la directive ne peut pas accéder à la variable `` nom '' sur la portée parent (c'est pourquoi, je pense, vous vouliez utiliser ng-model).
  • J'ai supprimé ngModel de votre directive et l'ai remplacé par un nom personnalisé que vous pouvez changer en n'importe quoi.
  • La chose qui fait que tout fonctionne toujours est que le signe '=' dans la portée. Extraire les documents docs sous l'en-tête «scope».

En général, vos directives doivent utiliser la portée isolée (ce que vous avez fait correctement) et utiliser la portée de type '=' si vous voulez qu'une valeur dans votre directive soit toujours mappée à une valeur dans la portée parent.


18
+1, mais je ne suis pas sûr d'être d'accord avec l'énoncé "ng-model consiste à lier des éléments HTML existants avec une valeur dans la portée". Les deux contenteditableexemples directive dans la documentation angulaire - page des formulaires , page NgModelController - à la fois l' utilisation modèle ng. Et la page ngModelController dit que ce contrôleur est "destiné à être étendu par d'autres directives".
Mark Rajcok

33
Je ne sais pas pourquoi cette réponse est si bien notée car elle n'accomplit pas ce que la question d'origine demandait - qui est d'utiliser ngModel. Oui, on peut éviter d'utiliser ngModel en mettant l'état dans le contrôleur parent mais cela se fait au détriment de deux contrôleurs étroitement liés et de ne pas pouvoir les utiliser / les réutiliser indépendamment. C'est comme utiliser une variable globale au lieu de configurer un écouteur entre deux composants - cela peut être techniquement plus simple mais ce n'est pas une bonne solution dans la plupart des cas.
Pat Niemeyer

J'ajouterais que s'il voulait s'appuyer sur le contrôleur parent, il devrait de toute façon lui injecter 'require: ^ parent' - afin qu'il puisse rendre la dépendance explicite et facultative si vous le souhaitez.
Pat Niemeyer

3
@Jeroen La façon dont je le vois, le principal avantage est la cohérence avec d'autres endroits où le modèle est transmis en tant que hg-model(et non la question du couplage, IMO). De cette façon, le contexte de données utilise toujours ng-model, qu'il s'agisse d' <input>une directive personnalisée ou personnalisée, simplifiant ainsi la surcharge cognitive pour le rédacteur HTML. C'est-à-dire que cela évite au rédacteur HTML d'avoir à trouver le nom my-directive-varde chaque directive, d'autant plus qu'il n'y a pas de saisie semi-automatique pour vous aider.
zai chang

2
euh ... ok ... mais maintenant cela ne fonctionne plus avec ng-model-optionsou avec les autres choses du modèle ng, n'est-ce pas?
George Mauer

68

J'ai pris un combo de toutes les réponses, et j'ai maintenant deux façons de le faire avec l'attribut ng-model:

  • Avec une nouvelle portée qui copie ngModel
  • Avec la même portée qui fait une compilation sur le lien

Je ne suis pas sûr que j'aime la compilation au moment du lien. Cependant, si vous remplacez simplement l'élément par un autre, vous n'avez pas besoin de le faire.

Dans l'ensemble, je préfère le premier. Définissez simplement la portée {ngModel:"="}et définissez ng-model="ngModel"où vous le souhaitez dans votre modèle.

Mise à jour : j'ai inséré l'extrait de code et l'ai mis à jour pour Angular v1.2. Il s'avère que la portée d'isoler est toujours la meilleure, en particulier lorsque vous n'utilisez pas jQuery. Cela se résume donc à:

  • Remplacez-vous un seul élément: remplacez-le simplement, laissez la portée seule, mais notez que replace est déconseillé pour la v2.0:

    app.directive('myReplacedDirective', function($compile) {
      return {
        restrict: 'E',
        template: '<input class="some">',
        replace: true
      };
    });
  • Sinon, utilisez ceci:

    app.directive('myDirectiveWithScope', function() {
      return {
        restrict: 'E',
        scope: {
          ngModel: '=',
        },
        template: '<div class="some"><input ng-model="ngModel"></div>'
      };
    });

1
J'ai mis à jour le plongeur avec les trois possibilités de portée et pour les éléments enfants du modèle ou l'élément racine du modèle.
w00t

1
C'est très bien, mais comment pouvez-vous essentiellement rendre cela facultatif? Je crée une directive de zone de texte pour une bibliothèque d'interface utilisateur et je veux que le modèle soit facultatif, ce qui signifie que la zone de texte fonctionnera toujours si le ngModel n'est pas défini.
Nick Radford

1
@NickRadford Vérifiez simplement si ngModel est défini sur la portée $ et sinon, ne l'utilisez pas?
w00t

1
Y aura-t-il des problèmes ou des frais généraux supplémentaires avec la réutilisation ng-modeldans une portée isolée?
Jeff Ling

2
@jeffling pas sûr mais je ne pense pas. La copie de ngModel est assez légère et la portée isolée limite l'exposition.
w00t

52

ce n'est pas si compliqué: dans votre dirctive, utilisez un alias: scope:{alias:'=ngModel'}

.directive('dateselect', function () {
return {
    restrict: 'E',
    transclude: true,
    scope:{
        bindModel:'=ngModel'
    },
    template:'<input ng-model="bindModel"/>'
}

dans votre html, utilisez comme d'habitude

<dateselect ng-model="birthday"></dateselect>

1
C'est tellement plus facile lorsque vous traitez avec des bibliothèques comme Kendo UI. Merci!
bytebender

30

Vous n'avez besoin de ng-model que lorsque vous devez accéder à $ viewValue ou $ modelValue du modèle. Voir NgModelController . Et dans ce cas, vous utiliseriez require: '^ngModel'.

Pour le reste, voir la réponse de Roys .


2
ng-model est également utile même si vous n'avez pas besoin de $ viewValue ou $ modelValue. Il est utile même si vous ne souhaitez que les fonctionnalités de liaison de données de ng-model, comme l'exemple de @ kolrie.
Mark Rajcok

1
Et le ^devrait être là uniquement si le modèle ng est appliqué dans un élément parent
georgiosd

18

C'est une petite réponse tardive, mais je l' ai trouvé génial ce post à propos NgModelController, que je pense est exactement ce que vous recherchez.

TL; DR - vous pouvez utiliser require: 'ngModel'puis ajouter NgModelControllerà votre fonction de liaison:

link: function(scope, iElement, iAttrs, ngModelCtrl) {
  //TODO
}

De cette façon, aucun piratage n'est nécessaire - vous utilisez le module intégré d'Angular ng-model



0

Depuis Angular 1.5, il est possible d'utiliser des composants. Les composants sont le chemin à parcourir et résout ce problème facilement.

<myComponent data-ng-model="$ctrl.result"></myComponent>

app.component("myComponent", {
    templateUrl: "yourTemplate.html",
    controller: YourController,
    bindings: {
        ngModel: "="
    }
});

Dans YourController, tout ce que vous devez faire est de:

this.ngModel = "x"; //$scope.$apply("$ctrl.ngModel"); if needed

Ce que j'ai trouvé, c'est que cela fonctionne si vous utilisez effectivement "=" plutôt que "<", ce qui est par ailleurs la meilleure pratique d'utilisation des composants. Je ne sais pas ce que signifie la partie "à l'intérieur de YourController" de cette réponse, le point de cela n'est pas de définir ngModel à l'intérieur du composant?
Marc Stober

1
@MarcStober Avec le "inside YourController", je voulais seulement montrer que le ngModel est disponible en tant que getter et setter. Dans cet exemple, le $ ctrl.result deviendra "x".
Niels Steenbeek

D'accord. Je pense que l'autre partie qui est importante est que vous pouvez également, dans votre modèle de contrôleur, le faire input ng-model="$ctrl.ngModel"et il se synchronisera également avec $ ctrl.result.
Marc Stober

0

La création d'une étendue isolée n'est pas souhaitable. J'éviterais d'utiliser l'attribut scope et ferais quelque chose comme ça. scope: true vous donne une nouvelle portée enfant mais pas un isolat. Utilisez ensuite l'analyse pour pointer une variable de portée locale vers le même objet que l'utilisateur a fourni à l'attribut ngModel.

app.directive('myDir', ['$parse', function ($parse) {
    return {
        restrict: 'EA',
        scope: true,
        link: function (scope, elem, attrs) {
            if(!attrs.ngModel) {return;}
            var model = $parse(attrs.ngModel);
            scope.model = model(scope);
        }
    };
}]);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.