Angular js init ng-model a partir de valores predeterminados

126

Digamos que tiene un formulario que tiene valores cargados de la base de datos. ¿Cómo se inicializa ng-model?

Ejemplo:

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

En mi controlador, $ scope.card no está definido inicialmente. ¿Hay alguna manera además de hacer algo como esto?

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

Respuestas:

136

Este es un error común en las nuevas aplicaciones angulares. No puede escribir sus valores en su HTML en el servidor si puede evitarlo. De hecho, si puede evitar que su servidor procese HTML por completo, mucho mejor.

Idealmente, desea enviar sus plantillas HTML angulares, luego baje sus valores a través de $ http en JSON y colóquelos en su alcance.

Entonces, si es posible, haga esto:

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

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

Si absolutamente DEBE representar sus valores en su HTML desde su servidor, puede colocarlos en una variable global y acceder a ellos con $ window:

En el encabezado de tu página escribirías:

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

Y luego en su controlador lo obtendría así:

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

Espero que eso ayude.

Ben Lesh
fuente
44
Sí, ayuda. Supongo que me sorprende que los chicos de Angular hayan tomado esta decisión.
Calvin Froedge
125
No se sorprenda ... La representación de HTML se está moviendo de los servidores al navegador. Hay docenas de marcos MVC en JavaScript en estos días, y es mucho más eficiente que un servidor aloje datos JSON / XML en aplicaciones JavaScript que renderizar cada página en el servidor. Desplaza gran parte del trabajo a la máquina del cliente en lugar de que el servidor reciba el golpe. Sin mencionar que ahorra en ancho de banda. Para colmo, podría tener una aplicación móvil nativa (o algo realmente) que consuma el mismo JSON a través de HTTP. Este es el futuro
Ben Lesh
3
@blesh: Gran respuesta. Muchas gracias. Estoy de acuerdo en que este es el camino a seguir y he comenzado a adoptar este enfoque yo mismo; ¿Tienes algún enlace que respalde esta afirmación? También me preocupa que mover la representación del html del servidor al cliente pueda reducir el tiempo de carga de la página, especialmente en dispositivos móviles en los que se podría generar una gran cantidad de HTML, por ejemplo, para un árbol de navegación.
GFoley83
19
No estoy de acuerdo con que esto sea un "error". En algunos casos, la representación del lado del cliente es la mejor solución. En otros, la representación del lado del servidor es el camino a seguir. Puede usar angular con ambas técnicas, y la representación del lado del cliente no debe mantenerse como la "forma angular", porque no lo es. Angular es más flexible que eso.
hunterloftis
8
@blesh, ciertamente: cualquier instancia en la que el SEO sea importante, pero el costo de contenido alternativo para los rastreadores es más alto que el costo de implementar angular con datos incrustados, cualquier aplicación que valore mucho la velocidad percibida o el tiempo de procesamiento para experiencia del usuario: muchos sitios de contenido y sitios de comercio electrónico se incluirían en una de estas categorías. Los ingenieros de twitter, que probaron el renderizado del cliente y luego regresaron al servidor para ofrecer una mejor experiencia de usuario, también describieron su razonamiento: blog.twitter.com/2012/improving-performance-twittercom
hunterloftis
236

Si no puede modificar su aplicación para hacer lo que @blesh sugiere (extraiga los datos JSON con $ http o $ resource y complete $ scope), puede usar ng-init en su lugar:

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

Consulte también AngularJS: ¿Se ignora el atributo de valor en un cuadro de texto de entrada cuando se utiliza un modelo ng?

Mark Rajcok
fuente
+1 para la forma angular (de la práctica menos preferida). Ejemplo: jsfiddle.net/9ymB3
John Lehmann
2
Estoy usando Angular con formularios web C # y encuentro que usar ng-inites bastante útil al establecer valores de código subyacente / devolución de datos, por ejemplo <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. ¿Un poco feo? Sí, pero hace el truco y resuelve un dolor de cabeza de integración en el proceso.
GFoley83
13
+1, elegante! La gente aboga por un comportamiento ágil y rápido, sin embargo, muchos aquí en SO dicen "hazlo todo del lado del cliente", como millones de llamadas http es bueno para sitios ágiles. La inserción del lado del servidor de datos en plantillas permite estrategias de almacenamiento en caché. Estático, o dinámico / parcial con Memcached. Esto es lo que hace Twitter. A veces es útil, otras veces no. Quería enfatizar eso. Solo tenía que agregar eso, no es que dijeras lo contrario, @Mark.
oma
1
En realidad lo retiro, esto funciona mejor para mí, increíble: stackoverflow.com/a/20522955/329367
Darren
1
FWIW: No me gusta la asignación de variables en expresiones de plantilla porque no es comprobable.
Ben Lesh
60

Obviamente, esta es una solución que falta, pero se agrega fácilmente para AngularJS. Simplemente escriba una directiva rápida para establecer el valor del modelo desde el campo de entrada.

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

Aquí está mi versión:

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
fuente
Buena directiva rápida. ¿Alguna buena razón para no empaquetar esto en ng-model para que value = y ng-model = puedan funcionar juntos como la mayoría de la gente esperaría?
hunterloftis
¡Bastante agradable! Esto es útil.
santiagobasulto
3
¡Gran respuesta! Acabo de agregar una "solución" para que esto también funcione con áreas de
Ryan Montgomery
¿Por qué definiste controllertu directiva y por qué no usaste el linkmétodo? Soy novato en angularjs
ebram khalil
1
necesitaba cambiar a val = $attrs.ngInitial || $attrs.value || $element.val()para que funcione con elementos seleccionados.
fjsj
12

En mi humilde opinión, la mejor solución es la directiva @Kevin Stone, pero tuve que actualizarla para que funcione en todas las condiciones (fe select, textarea), y esta está funcionando con seguridad:

    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
fuente
¿Y qué hay de seleccionar?
jwize 01 de
7

Puede usar una directiva personalizada (con soporte para textarea, select, radio y checkbox), consulte esta publicación de blog https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- campos-atributos / .

usuario1519240
fuente
1
Esta es una gran publicación. Mi respuesta favorita ya que la pregunta era establecer el valor de una entrada de acuerdo con los valores iniciales.
RPDeshaies
Creé un Plnkr para probar este código y funciona muy bien: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

También puede usar dentro de su código HTML: ng-init="card.description = 12345"

Angular no lo recomienda, y como se mencionó anteriormente, debe usar exclusivamente su controlador.

Pero funciona :)

koxon
fuente
4

Tengo un enfoque simple, porque tengo algunas validaciones y máscaras pesadas en mis formularios. Entonces, utilicé jquery para obtener mi valor nuevamente y disparar el evento "change" a validaciones:

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

Como otros señalaron, no es una buena práctica inicializar datos en vistas. Sin embargo, se recomienda inicializar los datos en los Controladores. (ver http://docs.angularjs.org/guide/controller )

Entonces puedes escribir

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

y

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

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

De esta forma, las vistas no contienen datos y el controlador inicializa el valor mientras se cargan los valores reales.

Jkarttunen
fuente
3

Si le gusta el enfoque de Kevin Stone arriba https://stackoverflow.com/a/17823590/584761 considere un enfoque más fácil escribiendo directivas para etiquetas específicas como '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 sigue esta ruta, no tendrá que preocuparse por agregar ng-initial a cada etiqueta. Establece automáticamente el valor del modelo en el atributo de valor de la etiqueta. Si no establece el atributo de valor, el valor predeterminado será una cadena vacía.

descifrador
fuente
3

Aquí hay un enfoque centrado en el servidor:

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

Es la misma idea básica que las respuestas más populares a esta pregunta, excepto que $ provide.value registra un servicio que contiene sus valores predeterminados.

Entonces, en el servidor, podría tener algo como:

{
    description: "Visa-4242"
}

Y colóquelo en su página a través de la tecnología del lado del servidor que elija. Aquí hay un resumen: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
fuente
1
Por lo que puedo decir, este es el método más angular para manejar el problema si hacer una solicitud inicial de $ http no es una opción. También tiene la ventaja de ser mucho más fácil de modificar si desea cambiar a $ http más tarde. Una posible mejora es devolver una promesa en lugar de facilitar aún más el cambio. Me daría mucho miedo establecer vars globales o usar ng-initial aquí.
Richard
1

Esta es una versión más genérica de las ideas mencionadas anteriormente ... Simplemente verifica si hay algún valor en el modelo, y si no, establece el valor para el modelo.

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 (ejemplo de uso):

<select class="form-control"
        ng-options="v for (i, v) in compressionMethods"
        ng-model="action.parameters.Method"
        default-value="'LZMA2'"></select>
Eitan HS
fuente
Esto funcionó para mí, pero solo después de pasar $scopea la llamada a getter()- ¡espero que esto aclare las cosas para cualquier otra persona que intente esto!
zesda
0

Probé lo que sugirió @Mark Rajcok. Está funcionando para valores de cadena (Visa-4242). Por favor, consulte este violín .

Desde el violín:

Lo mismo que se hace en el violín se puede hacer usando ng-repeat, lo que todos podrían recomendar. Pero después de leer la respuesta dada por @Mark Rajcok, solo quería probar lo mismo para un formulario con una variedad de perfiles. Las cosas funcionan bien hasta que tenga el $ scope.profiles = [{}, {}]; código en el controlador. Si elimino este código, obtengo errores. Pero en escenarios normales no puedo imprimir $scope.profiles = [{},{}]; como imprimo o echo html desde el servidor. ¿Será posible ejecutar lo anterior, de manera similar a como lo hizo @Mark Rajcok para los valores de cadena como <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, sin tener que hacer eco de la parte JavaScript del servidor.

Rajkamal Subramanian
fuente
1
Puede usar ng-init para inicializar la matriz y luego usar ng-repeat para generar las líneas de formulario: jsfiddle.net/6tP6x/1
Karen Zilles
0

Acabo de agregar soporte para el elemento seleccionado para "arreglar" Ryan Montgomery

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

Si tiene el valor init en la URL como mypage/id, entonces en el controlador del JS angular puede usar location.pathnamepara encontrar la identificación y asignarla al modelo que desee.

Neón
fuente