Formular an Richtlinie übergeben

75

Ich möchte meine Formularfelder in einer Direktive kapseln, damit ich dies einfach tun kann:

<div ng-form='myForm'>
  <my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>

</div>

Wie greife ich auf das myFormin meiner Direktive zu, um Validierungsprüfungen durchführen zu können, z myForm.Email.$valid.

Emad
quelle
basierend auf Antworten unter dem Winkel unterstützt keine dynamischen Formularelementnamen.
Emad
Es gibt jedoch eine Problemumgehung. Sehen Sie es hier Plunk , es basiert auf @tanguy_k Antwort
Ruslans Uralovs

Antworten:

154

So greifen Sie in einer Direktive auf den FormController zu:

require: '^form',

Dann wird es als 4. Argument für Ihre Link-Funktion verfügbar sein:

link: function(scope, element, attrs, formCtrl) {
    console.log(formCtrl);
}

fiddle

Möglicherweise benötigen Sie jedoch nur Zugriff auf den NgModelController:

require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
     console.log(ngModelCtrl);
}

fiddle

Wenn Sie Zugriff auf beide benötigen:

require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
    console.log(ctrls);
}

fiddle

Mark Rajcok
quelle
3
Wenn es Ihnen nichts ausmacht zu antworten, warum funktioniert das mit, '^form'aber nicht mit '^ngForm'? Ich habe versucht, das zum ersten Mal zum Laufen zu bringen, aber es kam zu einem Fehler "kein Controller". Ich mag deine Antwort viel besser als meine.
OverZealous
9
@OverZealous, anscheinend ist der Direktivenname, den Angular verwendet, wenn eine formoder ng-formDirektive im HTML gefunden wird, formeher als ngForm. Ich brauchte ein paar Versuche, um herauszufinden, dass der Name ist form. Ich denke, dies ist der Angular-Quellcode, in dem wir sehen, dass er formverwendet wird.
Mark Rajcok
2
@eibrahim, Angular unterstützt keine dynamischen Formularelementnamen.
Mark Rajcok
1
@MarkRajcok Der Zugriff auf das übergeordnete Formular innerhalb der Direktive ist nur die halbe Arbeit. Sie möchten dann $ invalid, $ error und friends verwenden. ~~ Und das funktioniert nicht, die einzige Lösung besteht darin, ein ng-Formular innerhalb der Direktive zu erstellen ~~ /! \ Ich habe mich geirrt, siehe meine Antwort unten: stackoverflow.com/questions/17618318/pass-form-to-directive /…
tanguy_k
3
@mtpultz, setzen Sie es auf den Bereich in Ihrer Link-Funktion : scope.formCtrl = formCtrl;, dann können Sie in Ihrem Controller darauf zugreifen, indem Sie $scope: controller: function($scope) { ... }. Beachten Sie jedoch, dass Ihr Direktiven-Controller zuerst ausgeführt wird, sodass die Referenz nicht vorhanden ist, wenn die Controller-Funktion zum ersten Mal ausgeführt wird.
Mark Rajcok
32

Hier ein vollständiges Beispiel (mit Bootstrap 3.1 gestaltet)

Es enthält ein Formular mit mehreren Eingaben (Name, E-Mail, Alter und Land). Name, E-Mail-Adresse und Alter sind Richtlinien. Land ist eine "normale" Eingabe.

Für jede Eingabe wird eine Hilfemeldung angezeigt, wenn der Benutzer keinen korrekten Wert eingibt.

Das Formular enthält eine Schaltfläche zum Speichern, die deaktiviert ist, wenn das Formular mindestens einen Fehler enthält.

<!-- index.html -->
<body ng-controller="AppCtrl">
  <script>
    var app = angular.module('app', []);

    app.controller('AppCtrl', function($scope) {
      $scope.person = {};
    });
  </script>
  <script src="inputName.js"></script>
  <script src="InputNameCtrl.js"></script>
  <!-- ... -->

  <form name="myForm" class="form-horizontal" novalidate>
    <div class="form-group">
      <input-name ng-model='person.name' required></input-name>
    </div>

    <!-- ... -->

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-4">
        <button class="btn btn-primary" ng-disabled="myForm.$invalid">
          <span class="glyphicon glyphicon-cloud-upload"></span> Save
        </button>
      </div>
    </div>
  </form>

  Person: <pre>{{person | json}}</pre>
  Form $error: <pre>{{myForm.$error | json}}</pre>
  <p>Is the form valid?: {{myForm.$valid}}</p>
  <p>Is name valid?: {{myForm.name.$valid}}</p>
</body>

// inputName.js
app.directive('inputName', function() {
  return {
    restrict: 'E',
    templateUrl: 'input-name.html',
    replace: false,
    controller: 'InputNameCtrl',
    require: ['^form', 'ngModel'],

    // See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
    scope: {},

    link: function(scope, element, attrs, ctrls) {
      scope.form = ctrls[0];
      var ngModel = ctrls[1];

      if (attrs.required !== undefined) {
        // If attribute required exists
        // ng-required takes a boolean
        scope.required = true;
      }

      scope.$watch('name', function() {
        ngModel.$setViewValue(scope.name);
      });
    }
  };
});

// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);

AngularJS-Formular mit Direktiven

tanguy_k
quelle
2
Dies ist ein großartiges Beispiel. Ich habe Ihren Plunk geändert, um Ihre Direktive allgemeiner zu gestalten, da sie jetzt jede Art von Eingabe unterstützen kann, z. B. Text oder Passwort oder E-Mail usw. Siehe hier: plnkr.co/edit/13rqpfrTiTwDMpCPmT7X?p=preview
Ruslans Uralovs
Dies scheint nicht zu funktionieren, wenn Sie dieselbe Direktive mehrmals in derselben Ansicht haben - dies liegt daran, dass der "Name" in der Direktive fest codiert ist
Marty
Am
Marty
Das funktioniert sehr gut, danke für die Veröffentlichung. Obwohl ich mich jetzt mit dem umgekehrten Problem befasse: Wenn der Objektwert außerhalb des Direktivenbereichs festgelegt wird, wird die Benutzeroberfläche nicht aktualisiert und die Daten werden "eingefroren", ohne dass sich dies erneut ändert. Beispiel hier: plnkr.co/edit/HLKKY1ZH0Kla93P2SmGj?p=preview Gibt es einen Hinweis, wie dies gelöst werden kann?
Rodrigo Brancher
Entschuldigung, ich habe vergessen, die (kleine) Plunk-Änderung zu erwähnen: Ich habe rechts neben der Schaltfläche " Senden" einen Link hinzugefügt, der den Namen der Person programmgesteuert ändert.
Rodrigo Brancher
17

Bearbeiten 2: Ich werde meine Antwort hinterlassen, da sie aus anderen Gründen hilfreich sein könnte, aber die andere Antwort von Mark Rajcok ist das, was ich ursprünglich tun wollte, aber nicht zur Arbeit kam. Anscheinend wäre der übergeordnete Controller hier formnicht ngForm.


Sie können es mit einem Attribut in Ihrer Direktive übergeben, obwohl dies ziemlich ausführlich wird.

Beispiel

Hier ist eine funktionierende, vereinfachte jsFiddle .

Code

HTML:

<div ng-form="myForm">
    <my-input form="myForm"></my-input>
</div>

Wesentliche Teile der Richtlinie:

app.directive('myInput', function() {
    return {
        scope: {
            form: '='
        },
        link: function(scope, element, attrs) {
            console.log(scope.form);
        }
    };
});

Was ist los

Wir haben Angular gebeten, den im formAttribut genannten Bereichswert mithilfe von a an unseren isolierten Bereich zu binden '='.

Auf diese Weise wird die tatsächliche Form von der Eingabeanweisung entkoppelt.

Hinweis: Ich habe versucht, zu verwenden require: "^ngForm", aber die ngForm-Direktive definiert keinen Controller und kann nicht auf diese Weise verwendet werden (was zu schade ist).


Trotzdem denke ich, dass dies eine sehr ausführliche und unordentliche Art ist, damit umzugehen. Es ist möglicherweise besser, dem Formularelement eine neue Direktive hinzuzufügen und requireauf dieses Element zuzugreifen. Ich werde sehen, ob ich etwas zusammenstellen kann.

Bearbeiten: Verwenden einer übergeordneten Direktive

OK, hier ist das Beste, was ich mit einer übergeordneten Direktive herausfinden kann. Ich werde gleich mehr erklären:

Arbeits jsFiddle Mutter Direktive

HTML:

<div ng-app="myApp">
    <div ng-form="theForm">
        <my-form form="theForm">
            <my-input></my-input>
        </my-form>
    </div>
</div>

JS (teilweise):

app.directive('myForm', function() {
    return {
        restrict: 'E',
        scope: {
            form: '='
        },
        controller: ['$scope', function($scope) {
            this.getForm = function() {
                return $scope.form;
            }
        }]
    }
});

app.directive('myInput', function() {
    return {
        require: '^myForm',
        link: function(scope, element, attrs, myForm) {
            console.log(myForm.getForm());
        }
    };
});

Dadurch wird das Formular im Bereich der übergeordneten Direktive ( myForm) gespeichert , und untergeordnete Direktiven können darauf zugreifen, indem sie das übergeordnete Formular ( require: '^myForm') benötigen und in der Verknüpfungsfunktion ( myForm.getForm()) auf den Controller der Direktive zugreifen .

Leistungen:

  • Sie müssen das Formular nur an einer Stelle identifizieren
  • Sie können Ihren übergeordneten Controller verwenden, um allgemeinen Code zu speichern

Negative:

  • Sie benötigen einen zusätzlichen Knoten
  • Sie müssen den Formularnamen zweimal eingeben

Was ich bevorzugen würde

Ich habe versucht, es mithilfe eines Attributs für das Formularelement zum Laufen zu bringen . Wenn dies funktioniert, müssen Sie die Direktive nur demselben Element wie hinzufügen ngForm.

Ich bekam jedoch ein seltsames Verhalten mit dem Bereich, in dem die myFormNameVariable sichtbar sein $scopewürde, aber undefinedwenn ich versuchte, darauf zuzugreifen. Das hat mich verwirrt.

Übereifrig
quelle
Vielen Dank für Ihre Antwort ... das Problem ist in meiner Direktive Ich kann scope.form bekommen, aber ich kann nicht zu scope.form.Email
Emad
Dies ist, was ich tue jsfiddle.net/SwEM6/1 (beachten Sie, dass der Name der Eingabe aus der Direktivenbereichseigenschaft {{name}} "berechnet" wird, aber wenn ich das in einen fest codierten Wert ändere, z. B. "Email", dann es funktioniert ... irgendeine Idee?
Emad
Sie suchen nach einer Möglichkeit, dynamische Formularnamen zu erstellen, dh name="{{name1}}"ich habe noch keine Antwort darauf gefunden.
rGil
1
Sprach zu früh. Diese Lösung mag für Sie funktionieren, ist aber etwas kompliziert. @Overzealous Sie möchten dies möglicherweise zu Ihrer Antwort hinzufügen.
rGil
Ich würde mir Mark Rajcoks Antwort unten ansehen. Ich habe anscheinend nicht auf die richtige Anweisung abgezielt.
OverZealous
8

Ab AngularJS 1.5.0 gibt es dafür eine viel sauberere Lösung (im Gegensatz zur linkdirekten Verwendung der Funktion). Wenn Sie auf ein Formular FormControllerim Direktiven-Controller Ihrer Unterkomponente zugreifen möchten , können Sie das requireAttribut einfach wie folgt auf die Direktive schlagen :

return {
  restrict : 'EA',
  require : {
    form : '^'
  },
  controller : MyDirectiveController,
  controllerAs : 'vm',
  bindToController : true,
  ...
};

Als Nächstes können Sie in Ihrer Vorlage oder in Ihrem Direktiven-Controller wie bei jeder anderen Bereichsvariablen darauf zugreifen, z .

function MyDirectiveController() {
  var vm = this;
  console.log('Is the form valid? - %s', vm.form.$valid);
}

Beachten Sie, dass für diese Funktion auch das bindToController: trueAttribut in Ihrer Direktive festgelegt sein muss. Weitere Informationen finden Sie in der Dokumentation zu $compileund in dieser Frage.

Relevante Teile aus der Dokumentation:

benötigen

Benötigen Sie eine andere Direktive und fügen Sie ihren Controller als viertes Argument in die Verknüpfungsfunktion ein. Die require-Eigenschaft kann eine Zeichenfolge , ein Array oder ein Objekt sein :

Wenn die Eigenschaft require ein Objekt ist und bindToControllerwahr ist, werden die erforderlichen Controller mithilfe der Schlüssel der Eigenschaft require an den Controller gebunden. Wenn der Name des erforderlichen Controllers mit dem lokalen Namen (dem Schlüssel) übereinstimmt, kann der Name weggelassen werden. Zum Beispiel {parentDir: '^parentDir'}ist äquivalent zu {parentDir: '^'}.

Priidu Neemre
quelle
Das sah fast richtig aus, funktioniert aber bei mir nicht ganz. Wenn ich nur die erforderliche Eigenschaft habe, wird sie an eine Verknüpfungsfunktion übergeben, aber nicht an den Controller gebunden (bindToController ist wahr). Liegt es daran, den Bereich nicht zu isolieren?
Darren Clark
Funktioniert gut und ist sehr elegant. Für mich funktioniert es nicht mit {parentDir: '^'}, sondern nur mit {parentDir: '^ parentDir'}.
Jowy
Dies war ein guter Ausgangspunkt für mich. Allerdings lief ich in Probleme , weil meine Richtlinie einen hat isoliert Umfang , in diesem Fall habe ich nicht verwenden bindToController und verwendet die erfordern Controller im vierten Argument des Link - Block.
Gabriel Gates
2

Hat deine Geigenarbeit "Was ich vorziehen würde" gemacht! Aus irgendeinem Grund konnte die Zeichenfolge "$ scope.ngForm" in einem console.log angezeigt werden, aber die direkte Protokollierung funktionierte nicht, was zu undefiniert führte. Sie können es jedoch erhalten, wenn Sie Attribute an die Controller-Funktion übergeben.

app.directive('myForm', function() {
return {
    restrict: 'A',
    controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
        this.getForm = function() {
            return $scope[$attrs['ngForm']];
        }
    }]
}
});

http://jsfiddle.net/vZ6MD/20/

LobotomyKid
quelle