Angular js init ng-model à partir des valeurs par défaut

126

Supposons que vous ayez un formulaire dont les valeurs sont chargées à partir de la base de données. Comment initialisez-vous ng-model?

Exemple:

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

Dans mon contrôleur, $ scope.card n'est pas défini au départ. Y a-t-il un autre moyen de faire quelque chose comme ça?

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

Réponses:

136

C'est une erreur courante dans les nouvelles applications angulaires. Vous ne voulez pas écrire vos valeurs dans votre HTML sur le serveur si vous pouvez l'éviter. En fait, si vous pouvez éviter que votre serveur ne rende entièrement le HTML, tant mieux.

Idéalement, vous souhaitez envoyer vos modèles Angular HTML, puis extraire vos valeurs via $ http dans JSON et les mettre dans votre portée.

Donc, si possible, faites ceci:

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

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

Si vous DEVEZ absolument rendre vos valeurs dans votre HTML à partir de votre serveur, vous pouvez les mettre dans une variable globale et y accéder avec $ window:

Dans l'en-tête de votre page, vous écrivez:

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

Et puis dans votre contrôleur, vous l'obtiendrez comme ceci:

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

J'espère que cela aide.

Ben Lesh
la source
4
Oui, ça aide. Je suppose que je suis juste choqué que les gars Angular aient pris cette décision.
Calvin Froedge
125
Ne soyez pas choqué ... Le rendu du HTML se déplace des serveurs vers le navigateur. Il existe des dizaines de frameworks MVC en JavaScript ces jours-ci, et il est beaucoup plus efficace pour un serveur d'héberger simplement des données JSON / XML vers des applications JavaScript que de rendre chaque page du serveur. Cela compense une grande partie du travail sur la machine du client plutôt que de laisser le serveur prendre le coup. Sans oublier qu'il économise de la bande passante. Pour couronner le tout, vous pourriez avoir une application mobile native (ou quelque chose de vraiment) qui consomme le même JSON sur HTTP. Tel est le futur.
Ben Lesh
3
@blesh: Excellente réponse. Merci beaucoup. Je conviens que c'est la voie à suivre et j'ai commencé à adopter cette approche moi-même; avez-vous des liens qui soutiennent cette affirmation? Je crains également que le déplacement du rendu du code HTML du serveur vers le client n'entraîne des temps de chargement des pages plus lents, en particulier sur les appareils mobiles où vous pourriez rendre beaucoup de HTML, par exemple pour un arbre de navigation.
GFoley83
19
Je ne suis pas d'accord pour dire que c'est une «erreur». Dans certains cas, le rendu côté client est la meilleure solution. Dans d'autres, le rendu côté serveur est la voie à suivre. Vous pouvez utiliser angulaire avec les deux techniques, et le rendu côté client ne doit pas être considéré comme la «voie angulaire», car ce n'est pas le cas. Angular est plus flexible que cela.
hunterloftis
8
@blesh, certainement - toute instance où le référencement est important mais le coût du contenu alternatif pour les robots d'exploration est plus élevé que le coût de mise en œuvre angulaire avec des données intégrées - toute application qui accorde une valeur très élevée à la vitesse perçue ou au temps de rendu pour le expérience utilisateur - de nombreux sites de contenu et sites de commerce électronique entreraient dans l'une de ces catégories. Les ingénieurs de Twitter, qui ont essayé le rendu client puis sont revenus sur le serveur pour offrir une meilleure expérience utilisateur, ont également exposé leur raisonnement: blog.twitter.com/2012/improving-performance-twittercom
hunterloftis
236

Si vous ne pouvez pas retravailler votre application pour faire ce que @blesh suggère (extraire les données JSON avec $ http ou $ resource et remplir $ scope), vous pouvez utiliser ng-init à la place:

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

Voir aussi AngularJS - L'attribut de valeur sur une zone de texte d'entrée est ignoré quand un modèle ng est utilisé?

Mark Rajcok
la source
+1 pour la voie angulaire (de la pratique la moins préférée). Exemple: jsfiddle.net/9ymB3
John Lehmann
2
J'utilise Angular avec des formulaires Web C # et je trouve que l'utilisation ng-initest très utile lors de la définition de valeurs à partir de code-behind / postback, par exemple <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. Un peu moche? Oui, mais fait l'affaire et résout un mal de tête d'intégration dans le processus.
GFoley83
13
+1, élégant! Les gens préconisent un comportement vif et rapide, mais tant de personnes ici à SO disent "faites tout du côté client", comme des millions d'appels http sont bons pour les sites accrocheurs. L'amorçage du côté serveur de données dans des modèles active les stratégies de mise en cache. Statique, ou dynamique / partielle avec Memcached. C'est ce que fait Twitter. Parfois, c'est utile, d'autres fois non. Je voulais souligner cela. Je devais juste ajouter cela, pas que vous ayez dit le contraire, @ Mark.
oma
1
En fait, je reprends ça, cela fonctionne mieux pour moi, génial: stackoverflow.com/a/20522955/329367
Darren
1
FWIW: Je n'aime pas l'affectation de variable dans les expressions de modèle car elle n'est pas testable.
Ben Lesh
60

C'est un correctif manifestement manquant, mais facilement ajouté pour AngularJS. Écrivez simplement une directive rapide pour définir la valeur du modèle à partir du champ de saisie.

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

Voici ma 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
la source
Belle directive rapide. Y a-t-il une bonne raison de ne pas intégrer cela dans ng-model afin que value = et ng-model = puissent fonctionner ensemble comme la plupart des gens s'y attendraient?
hunterloftis
Pas mal! C'est utile.
santiagobasulto
3
Très bonne réponse! Je viens d'ajouter un «correctif» pour que cela fonctionne également avec les zones de texte - gist.github.com/rmontgomery429/6191275
Ryan Montgomery
pourquoi vous avez défini controllerpour votre directive, et pourquoi ne pas utiliser la linkméthode ?. Je suis novice chez angularjs
ebram khalil
1
nécessaire de changer val = $attrs.ngInitial || $attrs.value || $element.val()pour qu'il fonctionne avec des éléments sélectionnés.
fjsj
12

À mon humble avis, la meilleure solution est la directive @Kevin Stone, mais j'ai dû la mettre à niveau pour qu'elle fonctionne dans toutes les conditions (par exemple, sélectionnez, textarea), et celle-ci fonctionne à coup sûr:

    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
la source
Et qu'en est-il de sélectionner?
jwize
7

Vous pouvez utiliser une directive personnalisée (avec prise en charge de textarea, select, radio et checkbox), consultez ce billet de blog https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- champs-attributs / .

utilisateur1519240
la source
1
C'est un excellent article. Ma réponse préférée depuis la question était de définir la valeur d'une entrée en fonction des valeurs initiales.
RPDeshaies
J'ai créé un Plnkr pour tester ce code et cela fonctionne très bien: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

Vous pouvez également utiliser dans votre code HTML: ng-init="card.description = 12345"

Cela n'est pas recommandé par Angular, et comme mentionné ci-dessus, vous devez utiliser exclusivement votre contrôleur.

Mais ça marche :)

Koxon
la source
4

J'ai une approche simple, car j'ai des validations et des masques lourds dans mes formulaires. J'ai donc utilisé jquery pour récupérer ma valeur et déclencher l'événement "change" pour les validations:

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

Comme d'autres l'ont souligné, l'initialisation des données sur les vues n'est pas une bonne pratique. Cependant, l'initialisation des données sur les contrôleurs est recommandée. (voir http://docs.angularjs.org/guide/controller )

Pour que tu puisses écrire

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

et

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

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

De cette façon, les vues ne contiennent pas de données et le contrôleur initialise la valeur pendant le chargement des valeurs réelles.

Jkarttunen
la source
3

Si vous aimez l'approche de Kevin Stone ci-dessus https://stackoverflow.com/a/17823590/584761, envisagez une approche plus simple en écrivant des directives pour des balises spécifiques telles que 'input'.

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

Si vous suivez cette voie, vous n'aurez pas à vous soucier d'ajouter ng-initial à chaque balise. Il définit automatiquement la valeur du modèle sur l'attribut value de la balise. Si vous ne définissez pas l'attribut value, il sera par défaut une chaîne vide.

codeur
la source
3

Voici une approche centrée sur le serveur:

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

C'est la même idée de base que les réponses les plus populaires à cette question, à l'exception de $ provide.value enregistre un service qui contient vos valeurs par défaut.

Donc, sur le serveur, vous pourriez avoir quelque chose comme:

{
    description: "Visa-4242"
}

Et mettez-le dans votre page via la technologie côté serveur de votre choix. Voici un aperçu: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
la source
1
Pour autant que je sache, c'est la méthode la plus angulaire pour gérer le problème si faire une requête $ http initiale n'est pas une option. Il a également l'avantage d'être beaucoup plus facile à modifier si vous souhaitez passer ultérieurement à $ http. Une amélioration possible est de rendre une promesse au lieu de rendre le changement encore plus facile. J'éviterais beaucoup de définir des variables globales ou d'utiliser ng-initial ici.
richard
1

Celui-ci est une version plus générique des idées mentionnées ci-dessus ... Il vérifie simplement s'il y a une valeur dans le modèle, et sinon, il définit la valeur du modèle.

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 (exemple d'utilisation):

<select class="form-control"
        ng-options="v for (i, v) in compressionMethods"
        ng-model="action.parameters.Method"
        default-value="'LZMA2'"></select>
Eitan HS
la source
Cela a fonctionné pour moi, mais seulement après avoir passé $scopel'appel à getter()- j'espère que cela clarifiera les choses pour quiconque essaie cela!
zesda
0

J'ai essayé ce que @Mark Rajcok a suggéré. Il fonctionne pour les valeurs de chaîne (Visa-4242). Veuillez vous référer à ce violon .

Du violon:

La même chose qui est faite dans le violon peut être faite en utilisant ng-repeat, ce que tout le monde pourrait recommander. Mais après avoir lu la réponse donnée par @Mark Rajcok, je voulais juste essayer la même chose pour un formulaire avec un tableau de profils. Les choses fonctionnent bien jusqu'à ce que j'aie le $ scope.profiles = [{}, {}]; code dans le contrôleur. Si je supprime ce code, j'obtiens des erreurs. Mais dans des scénarios normaux, je ne peux pas imprimer $scope.profiles = [{},{}]; lorsque j'imprime ou que je fais écho au HTML du serveur. Sera-t-il possible d'exécuter ce qui précède, de la même manière que @Mark Rajcok l'a fait pour les valeurs de chaîne comme <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, sans avoir à faire écho à la partie JavaScript du serveur.

Rajkamal Subramanian
la source
1
Vous pouvez utiliser ng-init pour initialiser le tableau, puis utiliser ng-repeat pour afficher les lignes du formulaire: jsfiddle.net/6tP6x/1
Karen Zilles
0

Ajout de la prise en charge de l'élément sélectionné à Ryan Montgomery "fix"

<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
la source
0

Si vous avez la valeur init dans l'URL comme mypage/id, dans le contrôleur du JS angulaire, vous pouvez utiliser location.pathnamepour trouver l'id et l'assigner au modèle que vous voulez.

Néon
la source