AngularJS - Erstellen Sie eine Direktive, die das ng-Modell verwendet

294

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

Folgendes habe ich mir bisher ausgedacht:

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, ob dies der richtige Weg ist, um mit diesem Szenario umzugehen, und es gibt einen Fehler, dass mein Steuerelement nicht mit dem Wert des Zielfelds ng-model initialisiert wird.

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

Wie geht man damit richtig um?

BEARBEITEN : Nach dem Entfernen der ng-model="value"aus der Vorlage scheint dies gut zu funktionieren. Ich werde diese Frage jedoch offen halten, da ich überprüfen möchte, ob dies der richtige Weg ist.

kolrie
quelle
1
Was ist, wenn Sie es entfernen scopeund einstellen scope: false? Wie kann man sich ng-modelin diesem Fall binden ?
Saeed Neamati

Antworten:

210

EDIT : Diese Antwort ist alt und wahrscheinlich veraltet. Nur ein Kopf hoch, damit die Leute nicht in die Irre gehen. Ich verwende 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 wenig 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;
}

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

Folgendes sehe ich:

  • Ich verstehe, warum Sie 'ng-model' verwenden möchten, aber in Ihrem Fall ist dies nicht erforderlich. ng-model dient dazu, vorhandene HTML-Elemente mit einem Wert im Bereich zu verknüpfen . Da Sie selbst eine Direktive erstellen, erstellen Sie ein 'neues' HTML-Element, sodass Sie kein ng-Modell benötigen.

BEARBEITEN Wie von Mark in seinem Kommentar erwähnt, gibt es keinen Grund, warum Sie das ng-Modell nicht verwenden können , nur um die Konvention einzuhalten.

  • Durch explizites Erstellen eines Bereichs in Ihrer Direktive (eines "isolierten" Bereichs) kann der Bereich der Direktive nicht auf die Variable "name" im übergeordneten Bereich zugreifen (weshalb Sie, glaube ich, das ng-Modell 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 funktioniert, ist das '=' Zeichen im Bereich. Kasse der docs docs unter dem ‚Umfang‘ Header.

Im Allgemeinen sollten Ihre Anweisungen den isolierten Bereich verwenden (was Sie korrekt getan haben) und den Bereich vom Typ '=' verwenden, wenn ein Wert in Ihrer Anweisung immer einem Wert im übergeordneten Bereich zugeordnet werden soll.

Roy Truelove
quelle
18
+1, aber ich bin nicht sicher, ob ich der Aussage "ng-model soll vorhandene HTML-Elemente mit einem Wert im Bereich verknüpfen" zustimme. Die beiden contenteditableDirektivenbeispiele in den Angular-Dokumenten - Formularseite , NgModelController-Seite - verwenden beide ng-model. Auf der Seite ngModelController heißt es, dass dieser Controller "durch andere Anweisungen erweitert werden soll".
Mark Rajcok
33
Ich bin mir nicht sicher, warum diese Antwort so hoch bewertet wird, weil sie nicht das erfüllt, was die ursprüngliche Frage gestellt hat - nämlich ngModel zu verwenden. Ja, man kann die Verwendung von ngModel vermeiden, indem man den Status in den übergeordneten Controller einfügt. Dies geht jedoch zu Lasten der Tatsache, dass zwei Controller fest miteinander verbunden sind und sie nicht unabhängig voneinander verwenden / wiederverwenden können. Es ist wie die Verwendung einer globalen Variablen, anstatt einen Listener zwischen zwei Komponenten einzurichten - technisch mag es einfacher sein, aber in den meisten Fällen ist es keine gute Lösung.
Pat Niemeyer
Ich würde hinzufügen, wenn er sich auf den übergeordneten Controller verlassen möchte, sollte er ihm trotzdem 'require: ^ parent' hinzufügen - damit er die Abhängigkeit explizit und optional machen kann, falls gewünscht.
Pat Niemeyer
3
@Jeroen Aus meiner Sicht ist der Hauptvorteil die Konsistenz mit anderen Stellen, an denen das Modell als übergeben wird hg-model(und nicht das Problem der Kopplung, IMO). Auf diese Weise verwendet der Datenkontext immer das ng-Modell, unabhängig davon, ob es sich um eine <input>oder eine benutzerdefinierte Direktive handelt, wodurch der kognitive Aufwand für den HTML-Writer vereinfacht wird. Das heißt, es erspart dem HTML-Writer, herauszufinden, wie der Name my-directive-varfür jede Direktive lautet, zumal es keine automatische Vervollständigung gibt, die Ihnen hilft.
Zai Chang
2
ähm ... ok ... aber jetzt funktioniert das nicht mehr mit ng-model-optionsoder einem der anderen ng-Modellsachen, oder?
George Mauer
68

Ich habe eine Kombination aller Antworten erstellt und habe jetzt zwei Möglichkeiten, dies mit dem Attribut ng-model zu tun:

  • Mit einem neuen Bereich, der ngModel kopiert
  • Mit dem gleichen Umfang, der eine Kompilierung auf Link durchführt

Ich bin mir nicht sicher, ob mir das Kompilieren zur Linkzeit gefällt. Wenn Sie das Element jedoch nur durch ein anderes ersetzen, müssen Sie dies nicht tun.

Alles in allem bevorzuge ich den ersten. Stellen Sie einfach den Bereich auf {ngModel:"="}und legen ng-model="ngModel"Sie fest, wo Sie ihn in Ihrer Vorlage haben möchten.

Update : Ich habe das Code-Snippet eingefügt und es für Angular v1.2 aktualisiert. Es stellt sich heraus, dass der isolierte Bereich immer noch am besten ist, insbesondere wenn jQuery nicht verwendet wird. Es läuft also darauf hinaus:

  • Ersetzen Sie ein einzelnes Element: Ersetzen Sie es einfach, lassen Sie den Bereich in Ruhe, aber beachten Sie, dass das Ersetzen für Version 2.0 veraltet ist:

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

    app.directive('myDirectiveWithScope', function() {
      return {
        restrict: 'E',
        scope: {
          ngModel: '=',
        },
        template: '<div class="some"><input ng-model="ngModel"></div>'
      };
    });
w00t
quelle
1
Ich habe den Plunker mit allen drei Bereichsmöglichkeiten und für untergeordnete Elemente der Vorlage oder das Stammelement der Vorlage aktualisiert.
w00t
1
Das ist großartig, aber wie macht man das im Wesentlichen optional? Ich erstelle eine Textbox-Direktive für eine UI-Bibliothek und möchte, dass das Modell optional ist. Dies bedeutet, dass das Textfeld weiterhin funktioniert, wenn das ngModel nicht festgelegt ist.
Nick Radford
1
@NickRadford Überprüfen Sie einfach, ob ngModel im Bereich $ definiert ist, und verwenden Sie es nicht, wenn nicht?
w00t
1
Wird es Probleme oder zusätzlichen Aufwand bei der Wiederverwendung ng-modelin einem isolierten Bereich geben?
Jeff Ling
2
@ Jeffling nicht sicher, aber ich denke nicht. Das Kopieren von ngModel ist ziemlich leicht und der isolierte Bereich begrenzt die Belichtung.
w00t
52

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

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

Verwenden Sie in Ihrem HTML wie gewohnt

<dateselect ng-model="birthday"></dateselect>
AiShiguang
quelle
1
Dies ist viel einfacher, wenn Sie mit Bibliotheken wie Kendo UI arbeiten. Vielen Dank!
Bytebender
30

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

Für den Rest siehe Roys Antwort .

Asgoth
quelle
2
ng-model ist auch dann nützlich, wenn Sie $ viewValue oder $ modelValue nicht benötigen. Dies ist auch dann nützlich, wenn Sie nur die Datenbindungsfunktionen von ng-model verwenden möchten, wie im Beispiel von @ kolrie.
Mark Rajcok
1
Und das ^sollte nur da sein, wenn das ng-Modell in einem übergeordneten Element angewendet wird
georgiosd
18

Dies ist ein wenig zu spät Antwort, aber ich fand diese tolle Post über NgModelController, was ich denke , ist genau das, was Sie suchen.

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

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

Auf diese Weise sind keine Hacks erforderlich - Sie verwenden das integrierte Angular ng-model

Yaniv Efraim
quelle
2

Ich würde das ngmodel nicht über ein Attribut festlegen, 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

Mathew Berg
quelle
0

Seit Angular 1.5 können Komponenten verwendet werden. Komponenten sind der richtige Weg und lösen dieses Problem auf einfache Weise.

<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
Niels Steenbeek
quelle
Was ich gefunden habe ist, dass es funktioniert, wenn Sie tatsächlich "=" anstelle von "<" verwenden, was ansonsten die beste Vorgehensweise bei der Verwendung von Komponenten ist. Ich bin nicht sicher, was der Teil "innerhalb von YourController" dieser Antwort bedeutet. Der Sinn davon ist nicht, ngModel innerhalb der Komponente festzulegen.
Marc Stober
1
@MarcStober Mit dem "inside YourController" wollte ich nur zeigen, dass das ngModel als Getter und Setter verfügbar ist. In diesem Beispiel wird das Ergebnis $ ctrl.res zu "x".
Niels Steenbeek
OK. Ich denke, der andere wichtige Teil ist, dass Sie dies auch in Ihrer Controller-Vorlage tun können input ng-model="$ctrl.ngModel"und es wird auch mit $ ctrl.result synchronisiert.
Marc Stober
0

Das Erstellen eines isolierten Bereichs ist unerwünscht. Ich würde die Verwendung des Bereichsattributs vermeiden und so etwas tun. scope: true gibt Ihnen einen neuen untergeordneten Bereich, der jedoch nicht isoliert wird. Verwenden Sie dann parse, um eine lokale Bereichsvariable auf dasselbe Objekt zu verweisen, das der Benutzer dem ngModel-Attribut übergeben 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);
        }
    };
}]);
BTM1
quelle