Angular js init ng-model von Standardwerten

126

Angenommen, Sie haben ein Formular, in das Werte aus der Datenbank geladen wurden. Wie initialisiere ich ng-model?

Beispiel:

<input name="card[description]" ng-model="card.description" value="Visa-4242">

In meinem Controller ist $ scope.card anfangs undefiniert. Gibt es einen anderen Weg, als so etwas zu tun?

$scope.card = {
  description: $('myinput').val()
}
Calvin Froedge
quelle

Antworten:

136

Dies ist ein häufiger Fehler bei neuen Angular-Anwendungen. Sie möchten Ihre Werte nicht in Ihren HTML-Code auf dem Server schreiben, wenn Sie dies vermeiden können. Umso besser, wenn Sie es vermeiden können, dass Ihr Server HTML vollständig rendert.

Idealerweise möchten Sie Ihre Angular-HTML-Vorlagen versenden, dann Ihre Werte über $ http in JSON abrufen und in Ihren Bereich einfügen.

Wenn möglich, machen Sie Folgendes:

app.controller('MyController', function($scope, $http) {
    $http.get('/getCardInfo.php', function(data) {
       $scope.card = data;
    });
});

<input type="text" ng-model="card.description" />

Wenn Sie Ihre Werte unbedingt von Ihrem Server in Ihren HTML-Code rendern MÜSSEN, können Sie sie in eine globale Variable einfügen und mit $ window darauf zugreifen:

In die Kopfzeile Ihrer Seite würden Sie schreiben:

<head>
   <script>
       window.card = { description: 'foo' };
   </script>
</head>

Und dann würden Sie es in Ihrem Controller so bekommen:

app.controller('MyController', function($scope, $window) {
   $scope.card = $window.card;
});

Ich hoffe das hilft.

Ben Lesh
quelle
4
Ja, es hilft. Ich bin nur schockiert, dass die Angular-Leute diese Entscheidung getroffen haben.
Calvin Froedge
125
Seien Sie nicht schockiert ... Das Rendern von HTML wird von den Servern in den Browser verschoben. Heutzutage gibt es Dutzende von MVC-Frameworks in JavaScript, und es ist für einen Server viel effizienter, JSON / XML-Daten nur in JavaScript-Apps zu hosten, als jede einzelne Seite auf dem Server zu rendern. Es versetzt einen Großteil der Arbeit auf den Computer des Clients, anstatt dass der Server den Treffer erhält. Ganz zu schweigen davon, dass Bandbreite gespart wird. Um das Ganze abzurunden, könnten Sie eine native mobile App (oder irgendetwas wirklich) haben, die denselben JSON über HTTP verwendet. Das ist die Zukunft.
Ben Lesh
3
@blesh: Tolle Antwort. Vielen Dank. Ich stimme zu, dass dies der Weg in die Zukunft ist, und habe begonnen, diesen Ansatz selbst zu übernehmen. Haben Sie Links, die diese Behauptung unterstützen? Ich mache mir nur Sorgen, dass das Verschieben des Renderings des HTML-Codes vom Server auf den Client zu langsameren Ladezeiten der Seiten führen könnte, insbesondere auf Mobilgeräten, auf denen Sie möglicherweise viel HTML rendern, z. B. für einen Navigationsbaum.
GFoley83
19
Ich bin nicht der Meinung, dass dies ein "Fehler" ist. In einigen Fällen ist clientseitiges Rendern die beste Lösung. In anderen Fällen ist serverseitiges Rendern der richtige Weg. Sie können Angular mit beiden Techniken verwenden, und clientseitiges Rendern sollte nicht als "Angular Way" angesehen werden, da dies nicht der Fall ist. Angular ist flexibler.
Hunterloftis
8
@blesh, sicherlich - jeder Fall, in dem SEO wichtig ist, aber die Kosten für alternative Inhalte für Crawler höher sind als die Kosten für die Implementierung von Angular mit eingebetteten Daten - jede Anwendung, die einen sehr hohen Wert auf die wahrgenommene Geschwindigkeit oder die Zeit bis zum Rendern legt Benutzererfahrung - Viele Content-Websites und E-Commerce-Websites fallen in eine dieser Kategorien. Die Ingenieure von Twitter, die das Rendern von Clients ausprobiert haben und dann wieder auf den Server gewechselt sind
hunterloftis
236

Wenn Sie Ihre App nicht überarbeiten können, um das zu tun, was @blesh vorschlägt (JSON-Daten mit $ http oder $ resource nach unten ziehen und $ scope auffüllen), können Sie stattdessen ng-init verwenden:

<input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">

Siehe auch AngularJS - Wertattribut in einem Eingabetextfeld wird ignoriert, wenn ein ng-Modell verwendet wird.

Mark Rajcok
quelle
+1 für den Winkelweg (der weniger bevorzugten Praxis). Beispiel: jsfiddle.net/9ymB3
John Lehmann
2
Ich verwende Angular mit C # ng-init-Webformularen und finde, dass die Verwendung sehr nützlich ist, wenn Werte aus Code-Behind / Postback festgelegt werden <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. Etwas hässlich? Ja, aber der Trick & löst dabei Integrationsprobleme.
GFoley83
13
+1, elegant! Die Leute befürworten ein bissiges und schnelles Verhalten, aber so viele hier bei SO sagen "Mach alles auf der Clientseite.", Wie Millionen von http-Anrufen gut für bissige Websites sind. Das Einfügen der Datenserverseite in Vorlagen ermöglicht Caching-Strategien. Statisch oder dynamisch / partiell mit Memcached. Das macht Twitter. Manchmal ist es nützlich, manchmal nicht. Wollte das betonen. Musste nur hinzufügen, nicht dass du etwas anderes gesagt hast, @Mark.
Oma
1
Eigentlich nehme ich das zurück, das funktioniert am besten für mich, super
Darren
1
FWIW: Ich mag keine Variablenzuweisung in Vorlagenausdrücken, weil sie nicht testbar ist.
Ben Lesh
60

Dies ist eine offensichtlich fehlende, aber leicht hinzuzufügende Lösung für AngularJS. Schreiben Sie einfach eine Kurzanweisung, um den Modellwert aus dem Eingabefeld festzulegen.

<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>

Hier ist meine Version:

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

app.directive('ngInitial', function() {
  return {
    restrict: 'A',
    controller: [
      '$scope', '$element', '$attrs', '$parse', function($scope, $element, $attrs, $parse) {
        var getter, setter, val;
        val = $attrs.ngInitial || $attrs.value;
        getter = $parse($attrs.ngModel);
        setter = getter.assign;
        setter($scope, val);
      }
    ]
  };
});
Kevin Stone
quelle
Schöne schnelle Anweisung. Gibt es einen guten Grund, dies nicht in ng-model zu packen, damit value = und ng-model = so zusammenarbeiten können, wie es die meisten Leute erwarten würden?
Hunterloftis
Sehr schön! Das ist nützlich.
Santiagobasulto
3
Gute Antwort! Ich habe gerade einen "Fix" hinzugefügt, damit dies auch mit Textbereichen funktioniert
Ryan Montgomery
Warum haben Sie controllerfür Ihre Direktive definiert und warum haben Sie die linkMethode nicht verwendet ? Ich bin Neuling bei Angularjs
Ebram Khalil
1
musste geändert werden, val = $attrs.ngInitial || $attrs.value || $element.val()damit es mit ausgewählten Elementen funktioniert.
FJSJ
12

IMHO ist die beste Lösung die @ Kevin Stone-Direktive, aber ich musste sie aktualisieren, um unter allen Bedingungen (zB select, textarea) zu funktionieren, und diese funktioniert mit Sicherheit:

    angular.module('app').directive('ngInitial', function($parse) {
        return {
            restrict: "A",
            compile: function($element, $attrs) {
                var initialValue = $attrs.value || $element.val();
                return {
                    pre: function($scope, $element, $attrs) {
                        $parse($attrs.ngModel).assign($scope, initialValue);
                    }
                }
            }
        }
    });
jtompl
quelle
Und was ist mit select?
Jwize
7

Sie können eine benutzerdefinierte Direktive verwenden (mit Unterstützung für Textbereich, Auswahl, Radio und Kontrollkästchen). Lesen Sie diesen Blog-Beitrag unter https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- Felder-Attribute / .

user1519240
quelle
1
Dies ist ein großartiger Beitrag. Meine Lieblingsantwort seit der Frage war, den Wert einer Eingabe gemäß den Initialwerten festzulegen.
RPDeshaies
Ich habe ein Plnkr erstellt, um diesen Code zu testen, und es funktioniert hervorragend: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

Sie können auch in Ihrem HTML-Code Folgendes verwenden: ng-init="card.description = 12345"

Es wird von Angular nicht empfohlen, und wie oben erwähnt, sollten Sie ausschließlich Ihren Controller verwenden.

Aber es funktioniert :)

Koxon
quelle
4

Ich habe einen einfachen Ansatz, weil ich einige umfangreiche Validierungen und Masken in meinen Formularen habe. Also habe ich jquery verwendet, um meinen Wert wieder zu erhalten und das Ereignis "change" auf Validierungen auszulösen:

$('#myidelement').val('123');
$('#myidelement').trigger( "change");
Guilherme Redmer Machado
quelle
3

Wie andere betonten, ist es nicht empfehlenswert, Daten in Ansichten zu initialisieren. Es wird jedoch empfohlen, Daten auf Controllern zu initialisieren. (siehe http://docs.angularjs.org/guide/controller )

So können Sie schreiben

<input name="card[description]" ng-model="card.description">

und

$scope.card = { description: 'Visa-4242' };

$http.get('/getCardInfo.php', function(data) {
   $scope.card = data;
});

Auf diese Weise enthalten die Ansichten keine Daten, und der Controller initialisiert den Wert, während die realen Werte geladen werden.

Jkarttunen
quelle
3

Wenn Ihnen der Ansatz von Kevin Stone über https://stackoverflow.com/a/17823590/584761 gefällt, sollten Sie einen einfacheren Ansatz in Betracht ziehen, indem Sie Anweisungen für bestimmte Tags wie "Eingabe" schreiben.

app.directive('input', function ($parse) {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, element, attrs) {
            if (attrs.ngModel) {
                val = attrs.value || element.text();
                $parse(attrs.ngModel).assign(scope, val);
            }
        }
    }; });

Wenn Sie diesen Weg gehen, müssen Sie sich nicht darum kümmern, jedem Tag ng-initial hinzuzufügen. Der Wert des Modells wird automatisch auf das Wertattribut des Tags gesetzt. Wenn Sie das Wertattribut nicht festlegen, wird standardmäßig eine leere Zeichenfolge verwendet.

Codierer
quelle
3

Hier ist ein serverzentrierter Ansatz:

<html ng-app="project">
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <script>
        // Create your module
        var dependencies = [];
        var app = angular.module('project', dependencies);

        // Create a 'defaults' service
        app.value("defaults", /* your server-side JSON here */);

        // Create a controller that uses the service
        app.controller('PageController', function(defaults, $scope) {
            // Populate your model with the service
            $scope.card = defaults;
        });
    </script>

    <body>
        <div ng-controller="PageController">
            <!-- Bind with the standard ng-model approach -->
            <input type="text" ng-model="card.description">
        </div>
    </body>
</html>

Dies ist die gleiche Grundidee wie die gängigsten Antworten auf diese Frage, außer dass $ require.value einen Dienst registriert, der Ihre Standardwerte enthält.

Auf dem Server könnten Sie also Folgendes haben:

{
    description: "Visa-4242"
}

Und fügen Sie es über die serverseitige Technologie Ihrer Wahl in Ihre Seite ein. Hier ist eine Zusammenfassung: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
quelle
1
Soweit ich das beurteilen kann, ist dies die eckigste Methode, um das Problem zu lösen, wenn eine anfängliche $ http-Anfrage nicht möglich ist. Es hat auch den Vorteil, dass es viel einfacher zu ändern ist, wenn Sie später zu $ ​​http wechseln möchten. Eine mögliche Verbesserung besteht darin, stattdessen ein Versprechen zurückzugeben, um die Umstellung noch einfacher zu gestalten. Ich würde mich sehr scheuen, hier globale Variablen zu setzen oder ng-initial zu verwenden.
Richard
1

Dies ist eine allgemeinere Version der oben genannten Ideen ... Es wird lediglich geprüft, ob das Modell einen Wert enthält, und wenn nicht, wird der Wert auf das Modell festgelegt.

JS:

function defaultValueDirective() {
    return {
        restrict: 'A',
        controller: [
            '$scope', '$attrs', '$parse',
            function ($scope, $attrs, $parse) {
                var getter = $parse($attrs.ngModel);
                var setter = getter.assign;
                var value = getter();
                if (value === undefined || value === null) {
                    var defaultValueGetter = $parse($attrs.defaultValue);
                    setter($scope, defaultValueGetter());
                }
            }
        ]
    }
}

HTML (Anwendungsbeispiel):

<select class="form-control"
        ng-options="v for (i, v) in compressionMethods"
        ng-model="action.parameters.Method"
        default-value="'LZMA2'"></select>
Eitan HS
quelle
Das hat bei mir funktioniert, aber erst, nachdem ich $scopezum Anruf getter()übergegangen bin - hoffe, das klärt die Dinge für alle anderen, die dies versuchen!
Zesda
0

Ich habe versucht, was @ Mark Rajcok vorgeschlagen hat. Es funktioniert für String-Werte (Visa-4242). Bitte beziehen Sie sich auf diese Geige .

Aus der Geige:

Das gleiche, was in der Geige gemacht wird, kann mit gemacht werden ng-repeat, was jeder empfehlen kann. Aber nachdem ich die Antwort von @Mark Rajcok gelesen hatte, wollte ich dasselbe nur für ein Formular mit einer Reihe von Profilen versuchen. Die Dinge funktionieren gut, bis ich die $ scope.profiles = [{}, {}] habe; Code in der Steuerung. Wenn ich diesen Code entferne, bekomme ich Fehler. Aber in normalen Szenarien kann ich nicht drucken, $scope.profiles = [{},{}]; während ich HTML vom Server drucke oder wiedergebe. Wird es möglich sein, das oben Genannte auf ähnliche Weise wie @Mark Rajcok für die Zeichenfolgenwerte wie auszuführen <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, ohne den JavaScript-Teil vom Server wiederholen zu müssen?

Rajkamal Subramanian
quelle
1
Sie können ng-init verwenden, um das Array zu initialisieren, und dann ng-repeat verwenden, um die Formularzeilen auszugeben
Karen Zilles
0

Gerade Unterstützung für ausgewähltes Element zu Ryan Montgomery "Fix" hinzugefügt

<select class="input-control" ng-model="regCompModel.numberOfEmployeeId" ng-initial>
    <option value="1af38656-a752-4a98-a827-004a0767a52d"> More than 500</option>
    <option value="233a2783-db42-4fdb-b191-0f97d2d9fd43"> Between 250 and 500</option>
    <option value="2bab0669-550c-4555-ae9f-1fdafdb872e5"> Between 100 and 250</option>
    <option value="d471e43b-196c-46e0-9b32-21e24e5469b4"> Between 50 and 100</option>
    <option value="ccdad63f-69be-449f-8b2c-25f844dd19c1"> Between 20 and 50</option>
    <option value="e00637a2-e3e8-4883-9e11-94e58af6e4b7" selected> Less then 20</option>
</select>

app.directive('ngInitial', function () {
return {
    restrict: 'A',
    controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {
        val = $attrs.sbInitial || $attrs.value || $element.val() || $element.text()
        getter = $parse($attrs.ngModel)
        setter = getter.assign
        setter($scope, val)
    }]
}

});

Kolesso
quelle
0

Wenn Sie den Init-Wert in der URL wie haben mypage/id, können Sie im Controller des eckigen JS location.pathnamedie ID suchen und sie dem gewünschten Modell zuweisen.

Neon
quelle