Anpassen der Vorlage innerhalb einer Richtlinie

98

Ich habe ein Formular, das Markup von Bootstrap verwendet, wie das folgende:

<form class="form-horizontal">
  <fieldset>
    <legend>Legend text</legend>
    <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
        <p class="help-block">Supporting help text</p>
      </div>
    </div>
  </fieldset>
</form>

Es gibt eine Menge Boilerplate-Code, den ich auf eine neue Direktive reduzieren möchte - Formulareingabe wie folgt:

<form-input label="Name" form-id="nameInput"></form-input>

erzeugt:

   <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
      </div>
    </div>

Ich habe so viel Arbeit über eine einfache Vorlage.

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {
                label: 'bind',
                formId: 'bind'
            },
            template:   '<div class="control-group">' +
                            '<label class="control-label" for="{{formId}}">{{label}}</label>' +
                            '<div class="controls">' +
                                '<input type="text" class="input-xlarge" id="{{formId}}" name="{{formId}}">' +
                            '</div>' +
                        '</div>'

        }
    })

Wenn ich jedoch erweiterte Funktionen hinzufüge, stecke ich fest.

Wie kann ich Standardwerte in der Vorlage unterstützen?

Ich möchte den Parameter "type" als optionales Attribut in meiner Direktive verfügbar machen, z.

<form-input label="Password" form-id="password" type="password"/></form-input>
<form-input label="Email address" form-id="emailAddress" type="email" /></form-input>

Wenn jedoch nichts angegeben ist, möchte ich standardmäßig auf "text". Wie kann ich das unterstützen?

Wie kann ich die Vorlage basierend auf dem Vorhandensein / Fehlen von Attributen anpassen?

Ich möchte auch das Attribut "erforderlich" unterstützen können, wenn es vorhanden ist. Z.B:

<form-input label="Email address" form-id="emailAddress" type="email" required/></form-input>

Wenn requiredes in der Direktive vorhanden ist, möchte ich es dem <input />in der Ausgabe generierten hinzufügen und es ansonsten ignorieren. Ich bin mir nicht sicher, wie ich das erreichen soll.

Ich vermute, dass diese Anforderungen über eine einfache Vorlage hinausgegangen sind und die Vorkompilierungsphasen verwenden müssen, aber ich weiß nicht, wo ich anfangen soll.

Marty Pitt
quelle
Bin ich der einzige, der den Elefanten im Raum sieht :) -> Was ist, wenn typedynamisch über Bindung gesetzt wird, z. type="{{ $ctrl.myForm.myField.type}}"? Ich habe alle folgenden Methoden überprüft und konnte keine Lösung finden, die in diesem Szenario funktioniert. Es sieht so aus, als würde die Vorlagenfunktion Literalwerte der Attribute sehen, z. tAttr['type'] == '{{ $ctrl.myForm.myField.type }}'statt tAttr['type'] == 'password'. Ich bin verwirrt.
Dimitry K

Antworten:

211
angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        compile: function(element, attrs) {
            var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
            element.replaceWith(htmlText);
        }
    };
})
Misko Hevery
quelle
6
Dies ist ein wenig spät, aber wenn in htmlTextIhnen eine zusätzliche ng-clickirgendwo, wäre die einzige Änderung zu ersetzen element.replaceWith(htmlText)mit element.replaceWith($compile(htmlText))?
Jclancy
@Misko, du hast erwähnt, um den Umfang loszuwerden. Warum? Ich habe eine Direktive, die nicht kompiliert wird, wenn sie mit isoliertem Bereich verwendet wird.
Syam
1
Dies funktioniert nicht, wenn es htmlTexteine ng-transclude-Direktive enthält
Alp
3
Leider habe ich festgestellt, dass die Formularüberprüfung damit nicht zu funktionieren scheint. $errorFlags für die eingefügte Eingabe werden nie gesetzt. Ich musste dies innerhalb der Verknüpfungseigenschaft einer Direktive tun: $compile(htmlText)(scope,function(_el){ element.replaceWith(_el); });damit der Controller des Formulars seine neu gebildete Existenz erkennt und in die Validierung einbezieht. Ich konnte es nicht dazu bringen, in der Compile-Eigenschaft einer Direktive zu funktionieren.
Mekonroy
5
Okay, es ist 2015 da draußen und ich bin mir ziemlich sicher, dass etwas schrecklich falsch daran ist, Markups in Skripten manuell zu generieren .
Inkarnation
38

Ich habe versucht, die von Misko vorgeschlagene Lösung zu verwenden, aber in meiner Situation waren einige Attribute, die in meine HTML-Vorlage eingefügt werden mussten, selbst Anweisungen.

Leider haben nicht alle Anweisungen, auf die in der resultierenden Vorlage verwiesen wird, ordnungsgemäß funktioniert. Ich hatte nicht genug Zeit, um in den Winkelcode einzutauchen und die Grundursache herauszufinden, fand aber eine Problemumgehung, die möglicherweise hilfreich sein könnte.

Die Lösung bestand darin, den Code, der die HTML-Vorlage erstellt, von der Kompilierung in eine Vorlagenfunktion zu verschieben. Beispiel basierend auf Code von oben:

    angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        template: function(element, attrs) {
           var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
             return htmlText;
        }
        compile: function(element, attrs)
        {
           //do whatever else is necessary
        }
    }
})
Janusz Gryszko
quelle
Dies löste mein Problem mit einem eingebetteten ng-Klick in der Vorlage
Joshcomley
Danke, das hat auch bei mir funktioniert. Wollte eine Direktive umbrechen, um einige Standardattribute anzuwenden.
Martinoss
2
Danke, mir war nicht einmal bewusst, dass die Vorlage eine Funktion akzeptiert!
Jon Snow
2
Dies ist keine Problemumgehung. Es ist die richtige Antwort auf das OP. Das bedingte Erstellen einer Vorlage in Abhängigkeit von den Attributen des Elements ist der genaue Zweck einer Direktiven- / Komponentenvorlagenfunktion. Sie sollten dafür keine Kompilierung verwenden. Das Angular-Team unterstützt diesen Codierungsstil nachdrücklich (ohne Verwendung der Kompilierungsfunktion).
Jose.angel.jimenez
Dies sollte die richtige Antwort sein, auch wenn ich nicht wusste, dass die Vorlage eine Funktion übernimmt :)
NeverGiveUp161
5

Die obigen Antworten funktionieren leider nicht ganz. Insbesondere hat die Kompilierungsphase keinen Zugriff auf den Bereich, sodass Sie das Feld nicht basierend auf dynamischen Attributen anpassen können. Die Verwendung der Verknüpfungsstufe scheint die größte Flexibilität zu bieten (in Bezug auf das asynchrone Erstellen von Dom usw.). Der folgende Ansatz befasst sich mit folgenden Aspekten:

<!-- Usage: -->
<form>
  <form-field ng-model="formModel[field.attr]" field="field" ng-repeat="field in fields">
</form>
// directive
angular.module('app')
.directive('formField', function($compile, $parse) {
  return { 
    restrict: 'E', 
    compile: function(element, attrs) {
      var fieldGetter = $parse(attrs.field);

      return function (scope, element, attrs) {
        var template, field, id;
        field = fieldGetter(scope);
        template = '..your dom structure here...'
        element.replaceWith($compile(template)(scope));
      }
    }
  }
})

Ich habe einen Kern mit vollständigerem Code und einer Beschreibung des Ansatzes erstellt.

JoeS
quelle
schöner Ansatz. Leider erhalte ich bei der Verwendung mit ngTransclude den folgenden Fehler:Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Alp
und warum nicht einen isolierten Bereich mit 'field: "="' verwenden?
IttayD
Sehr schön danke! Leider ist Ihr schriftlicher Ansatz offline :(
Michiel
Sowohl das Wesentliche als auch das Schreiben sind tote Links.
Binki
4

Folgendes habe ich letztendlich verwendet.

Ich bin sehr neu bei AngularJS und würde gerne bessere / alternative Lösungen sehen.

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {},
            link: function(scope, element, attrs)
            {
                var type = attrs.type || 'text';
                var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
                var htmlText = '<div class="control-group">' +
                    '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                        '<div class="controls">' +
                        '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                        '</div>' +
                    '</div>';
                element.html(htmlText);
            }
        }
    })

Anwendungsbeispiel:

<form-input label="Application Name" form-id="appName" required/></form-input>
<form-input type="email" label="Email address" form-id="emailAddress" required/></form-input>
<form-input type="password" label="Password" form-id="password" /></form-input>
Marty Pitt
quelle
10
Eine bessere Lösung besteht darin, (1) eine Kompilierungsfunktion anstelle der Verknüpfungsfunktion zu verwenden und dort zu ersetzen. Die Vorlage funktioniert in Ihrem Fall nicht, da Sie sie anpassen möchten. (2) den Umfang loswerden:
Misko Hevery
@MiskoHevery Vielen Dank für das Feedback - würde es Ihnen etwas ausmachen zu erklären, warum eine Kompilierungsfunktion hier einer Linkfunktion vorgezogen wird?
Marty Pitt
4
Ich denke, dies ist die Antwort von docs.angularjs.org/guide/directive : "Jede Operation, die von der Instanz von Anweisungen gemeinsam genutzt werden kann [z. B. das Transformieren des Vorlagen-DOM], sollte aus Leistungsgründen in die Kompilierungsfunktion verschoben werden."
Mark Rajcok
@Marty Können Sie noch eine Ihrer benutzerdefinierten Eingaben an ein Modell binden? (dh <form-input ng-model="appName" label="Application Name" form-id="appName" required/></form-input>)
Jonathan Wilson
1
@MartyPitt Aus O'Reillys "AngularJS" -Buch: "Wir haben also die compilePhase, in der die Vorlage transformiert wird, und die linkPhase, in der die Daten in der Ansicht geändert werden . In diesem Sinne ist der Hauptunterschied zwischen dem compileund linkFunktionen , die in den Richtlinien ist , dass compileFunktionen befassen sich mit Transformieren der Vorlage selbst, und linkFunktionen befassen sich mit Herstellung einer dynamischen Verbindung zwischen Modell und Ansicht. Es ist in dieser zweiten Phase die Bereiche an den kompilierten angebracht sind linkFunktionen, und die Richtlinie wird Live durch Datenbindung "
Julian