wake-up-neo.net

AngularJS - Erstellen Sie eine Direktive, die ng-model verwendet

Ich versuche, eine Direktive zu erstellen, die ein Eingabefeld mit demselben ng-Modell wie das Element erstellt, das die Direktive erstellt.

Hier ist, was ich bisher herausgefunden habe:

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

Ich bin jedoch nicht sicher, dass dies der richtige Weg ist, um mit diesem Szenario umzugehen, und es gibt einen Fehler, bei dem mein Steuerelement nicht mit dem Wert des Zielfelds ng-model initialisiert wird.

Hier ist ein Plunker des obigen Codes: http://plnkr.co/edit/IvrDbJ

Was ist der richtige Umgang damit?

EDIT: Nachdem Sie den ng-model="value" aus der Vorlage entfernt haben, scheint dies gut zu funktionieren. Ich werde diese Frage jedoch offenhalten, weil ich noch einmal prüfen möchte, dass dies der richtige Weg ist.

289
kolrie

EDIT: Diese Antwort ist veraltet und wahrscheinlich veraltet. Nur ein Kopf nach oben, so dass es Leute nicht in die Irre führt. Ich benutze Angular nicht mehr, daher bin ich nicht in der Lage, Verbesserungen vorzunehmen.


Es ist eigentlich eine ziemlich gute Logik, aber Sie können die Dinge ein bisschen vereinfachen.

Richtlinie

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 mit Direktive

<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;
}

Mit diesem Plunker können Sie es in Aktion sehen.

Folgendes sehe ich:

  • Ich verstehe, warum Sie 'ng-model' verwenden möchten, aber in Ihrem Fall ist es nicht notwendig. ng-model soll vorhandene HTML-Elemente mit einem Wert im Gültigkeitsbereich verknüpfen. Da Sie selbst eine Direktive erstellen, erstellen Sie ein 'neues' HTML-Element, sodass Sie kein ng-model benötigen.

EDIT Wie Mark in seinem Kommentar erwähnt, gibt es keinen Grund, warum Sie (können) ng-model nicht verwenden, nur um die Konventionen einzuhalten.

  • Durch das explizite Erstellen eines Bereichs in Ihrer Direktive (ein 'isolierter' Geltungsbereich) kann der Geltungsbereich der Direktive nicht auf die 'name'-Variable im übergeordneten Gültigkeitsbereich zugreifen (weshalb Sie meiner Meinung nach ng-model verwenden wollten).
  • Ich habe ngModel aus Ihrer Direktive entfernt und durch einen benutzerdefinierten Namen ersetzt, den Sie in einen beliebigen Namen ändern können.
  • Die Sache, die alles noch funktionieren lässt, ist das '=' Zeichen im Gültigkeitsbereich. Überprüfen Sie die docs docs unter dem 'scope' -Header.

Im Allgemeinen sollten Ihre Direktiven den isolierten Bereich verwenden (was Sie richtig gemacht haben) und den Typbereich '=' verwenden, wenn ein Wert in Ihrer Direktive immer einem Wert im übergeordneten Bereich zugeordnet werden soll. 

207
Roy Truelove

Ich habe eine Kombination aus allen Antworten genommen und habe nun zwei Möglichkeiten, dies mit dem ng-model-Attribut zu tun:

  • Mit einem neuen Geltungsbereich, der ngModel kopiert
  • Mit dem gleichen Umfang, der beim Link kompiliert wird

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

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

app.directive('myDirectiveWithScope', function() {
  return {
    restrict: 'E',
    scope: {
      ngModel: '=',
    },
    // Notice how label isn't copied
    template: '<div class="some"><label>{{label}}: <input ng-model="ngModel"></label></div>',
    replace: true
  };
});
app.directive('myDirectiveWithChildScope', function($compile) {
  return {
    restrict: 'E',
    scope: true,
    // Notice how label is visible in the scope
    template: '<div class="some"><label>{{label}}: <input></label></div>',
    replace: true,
    link: function ($scope, element) {
      // element will be the div which gets the ng-model on the original directive
      var model = element.attr('ng-model');
      $('input',element).attr('ng-model', model);
      return $compile(element)($scope);
    }
  };
});
app.directive('myDirectiveWithoutScope', function($compile) {
  return {
    restrict: 'E',
    template: '<div class="some"><label>{{$parent.label}}: <input></label></div>',
    replace: true,
    link: function ($scope, element) {
      // element will be the div which gets the ng-model on the original directive
      var model = element.attr('ng-model');
      return $compile($('input',element).attr('ng-model', model))($scope);
    }
  };
});
app.directive('myReplacedDirectiveIsolate', function($compile) {
  return {
    restrict: 'E',
    scope: {},
    template: '<input class="some">',
    replace: true
  };
});
app.directive('myReplacedDirectiveChild', function($compile) {
  return {
    restrict: 'E',
    scope: true,
    template: '<input class="some">',
    replace: true
  };
});
app.directive('myReplacedDirective', function($compile) {
  return {
    restrict: 'E',
    template: '<input class="some">',
    replace: true
  };
});
.some {
  border: 1px solid #cacaca;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div ng-app="model" ng-controller="MainCtrl">
  This scope value <input ng-model="name">, label: "{{label}}"
  <ul>
    <li>With new isolate scope (label from parent):
      <my-directive-with-scope ng-model="name"></my-directive-with-scope>
    </li>
    <li>With new child scope:
      <my-directive-with-child-scope ng-model="name"></my-directive-with-child-scope>
    </li>
    <li>Same scope:
      <my-directive-without-scope ng-model="name"></my-directive-without-scope>
    </li>
    <li>Replaced element, isolate scope:
      <my-replaced-directive-isolate ng-model="name"></my-replaced-directive-isolate>
    </li>
    <li>Replaced element, child scope:
      <my-replaced-directive-child ng-model="name"></my-replaced-directive-child>
    </li>
    <li>Replaced element, same scope:
      <my-replaced-directive ng-model="name"></my-replaced-directive>
    </li>
  </ul>
  <p>Try typing in the child scope ones, they copy the value into the child scope which breaks the link with the parent scope.
  <p>Also notice how removing jQuery makes it so only the new-isolate-scope version works.
  <p>Finally, note that the replace+isolate scope only works in AngularJS >=1.2.0
</div>

Ich bin mir nicht sicher, ob ich das Compilieren zur Linkzeit mag. Wenn Sie das Element jedoch nur durch ein anderes ersetzen, müssen Sie dies nicht tun.

Alles in allem bevorzuge ich den ersten. Setzen Sie einfach den Gültigkeitsbereich auf {ngModel:"="} und legen Sie ng-model="ngModel" dort fest, wo Sie es in Ihrer Vorlage haben möchten.

Update : Ich habe das Code-Snippet eingepflegt und für Angular v1.2 aktualisiert. Es stellt sich heraus, dass der Isolatbereich immer noch der beste ist, insbesondere wenn Sie jQuery nicht verwenden. Es läuft also darauf hinaus:

  • Ersetzen Sie ein einzelnes Element: Ersetzen Sie es einfach, lassen Sie den Gültigkeitsbereich unverändert, beachten Sie jedoch, dass Ersetzen für die Version 2.0 veraltet ist:

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

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

es ist nicht so kompliziert: Verwenden Sie in Ihrem Dirctive einen Alias: scope:{alias:'=ngModel'}

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

verwenden Sie in Ihrer HTML-Datei wie gewohnt

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

Sie benötigen ng-model nur, wenn Sie auf $ viewValue oder $ modelValue des Modells zugreifen müssen. Siehe NgModelController . In diesem Fall würden Sie require: '^ngModel' verwenden.

Für den Rest siehe Roys answer .

30
asgoth

Dies ist eine etwas verspätete Antwort, aber ich fand diese großartige post about NgModelController, die meiner Meinung nach genau das ist, wonach Sie gesucht haben. 

TL; DR - Sie können require: 'ngModel' verwenden und dann NgModelController zu Ihrer Verknüpfungsfunktion hinzufügen:

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

Auf diese Weise sind keine Hacks erforderlich - Sie verwenden Angulars ng-model

16
Yaniv Efraim

Ich würde das ngmodel nicht über ein Attribut setzen, Sie können es direkt in der Vorlage angeben:

template: '<div class="some"><label>{{label}}</label><input data-ng-model="ngModel"></div>',

plunker: http://plnkr.co/edit/9vtmnw?p=preview

2
Mathew Berg

Das Erstellen eines Isolat-Bereichs ist nicht wünschenswert. Ich würde die Verwendung des Bereichsattributs vermeiden und so etwas tun. scope: true gibt Ihnen einen neuen untergeordneten Bereich, aber nicht isoliert. Verwenden Sie dann parse, um eine lokale Gültigkeitsbereichsvariable auf das gleiche Objekt zu verweisen, das der Benutzer dem Attribut ngModel bereitgestellt hat.

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);
        }
    };
}]);
0
btm1

Seit Angular 1.5 können Komponenten verwendet werden. Komponenten sind der Weg zu gehen und lösen dieses Problem einfach.

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

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

In YourController müssen Sie lediglich Folgendes tun:

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