Js angular init-model a partir dos valores padrão

126

Digamos que você tenha um formulário com valores carregados do banco de dados. Como você inicializa o ng-model?

Exemplo:

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

No meu controlador, $ scope.card é indefinido inicialmente. Existe uma maneira de fazer algo assim?

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

Respostas:

136

Este é um erro comum em novas aplicações angulares. Você não deseja gravar seus valores em seu HTML no servidor, se puder evitá-lo. De fato, se você pode evitar que seu servidor renderize HTML inteiramente, tanto melhor.

Idealmente, você deseja enviar seus modelos HTML angulares, puxe seus valores por $ http em JSON e coloque-os em seu escopo.

Portanto, se possível, faça o seguinte:

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

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

Se você absolutamente DEVE renderizar seus valores em seu HTML a partir do seu servidor, você pode colocá-los em uma variável global e acessá-los com $ window:

No cabeçalho da sua página, você escreveria:

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

E então, no seu controlador, você o faria assim:

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

Espero que ajude.

Ben Lesh
fonte
4
Sim, isso ajuda. Acho que estou chocado que os caras do Angular tenham tomado essa decisão.
Calvin Froedge
125
Não fique chocado ... A renderização de HTML está saindo dos servidores e para o navegador. Atualmente, existem dezenas de estruturas MVC em JavaScript e é muito mais eficiente para um servidor hospedar dados JSON / XML em aplicativos JavaScript do que renderizar todas as páginas do servidor. Isso compensa grande parte do trabalho na máquina do cliente, em vez de fazer com que o servidor seja atingido. Sem mencionar que economiza largura de banda. Além disso, você pode ter um aplicativo móvel nativo (ou qualquer coisa realmente) que consuma o mesmo JSON sobre HTTP. Esse é o futuro
Ben Lesh
3
@blesh: Ótima resposta. Muito obrigado. Concordo que esse é o caminho a seguir e comecei a adotar essa abordagem; você tem algum link que suporte esta reivindicação? Também estou preocupado que mover a renderização do html do servidor para o cliente possa resultar em tempos de carregamento de página mais lentos, especialmente em dispositivos móveis onde você pode renderizar muito HTML, por exemplo, para uma árvore de navegação.
precisa saber é o seguinte
19
Não concordo que seja um "erro". Em alguns casos, a renderização do lado do cliente é a melhor solução. Em outros, a renderização do lado do servidor é o caminho a percorrer. Você pode usar angular com ambas as técnicas, e a renderização do lado do cliente não deve ser considerada como a "maneira angular", porque não é. Angular é mais flexível que isso.
Hunterloftis 31/07/2013
8
@blesh, certamente - qualquer instância em que o SEO seja importante, mas o custo de conteúdo alternativo para os rastreadores for maior que o custo da implementação angular com dados incorporados - qualquer aplicativo que valorize muito a velocidade percebida ou o tempo de renderização do experiência do usuário - muitos sites de conteúdo e de comércio eletrônico se enquadram em uma dessas categorias. Os engenheiros da twitter, que tentou cliente renderização de volta, em seguida, mudou-se para o servidor para entregar uma melhor experiência do usuário, esbocei o seu raciocínio, bem como: blog.twitter.com/2012/improving-performance-twittercom
hunterloftis
236

Se você não conseguir refazer o aplicativo para fazer o que o @blesh sugere (puxe os dados JSON para baixo com $ http ou $ resource e preencha o $ scope), use o ng-init em vez disso:

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

Consulte também AngularJS - O atributo Value em uma caixa de texto de entrada é ignorado quando há um modelo ng usado?

Mark Rajcok
fonte
+1 para o caminho angular (da prática menos preferida). Exemplo: jsfiddle.net/9ymB3
John Lehmann
2
Estou usando Angular com formulários da web C # e acho que o uso ng-inité bastante útil ao definir valores de code-behind / postback <input name="phone" data-ng-model="frm.phone" data-ng-init="frm.phone= '<%: Model.Phone %>'" data-ng-pattern="/^[0-9() \-+]+$/" type="tel" required />. Um pouco feio? Sim, mas faz o truque e resolve uma dor de cabeça de integração no processo.
GFoley83
13
+1, elegante! As pessoas defendem um comportamento rápido e rápido, mas muitos aqui na SO dizem "faça tudo do lado do cliente". Como milhões de chamadas http são boas para sites rápidos. A semeadura do lado do servidor de dados em modelos permite estratégias de armazenamento em cache. Estático ou dinâmico / parcial com Memcached. É isso que o twitter faz. Às vezes é útil, outras vezes não. Queria enfatizar isso. Só precisava acrescentar isso, não que você dissesse o contrário, @ Mark.
oma
1
Na verdade eu retiro isso, isso funciona melhor para mim, incrível: stackoverflow.com/a/20522955/329367
Darren
1
FWIW: Não gosto de atribuição de variável em expressões de modelo porque não é testável.
Ben Lesh
60

Esta é uma correção obviamente ausente, mas facilmente adicionada para o AngularJS. Basta escrever uma diretiva rápida para definir o valor do modelo no campo de entrada.

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

Aqui está a minha versão:

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
fonte
Diretiva rápida agradável. Alguma boa razão para não empacotar isso no modelo ng, de modo que value = e ng-model = possam trabalhar juntos como a maioria das pessoas esperaria?
Hunterloftis
Muito bom! Isso é útil.
Santiagobasulto
3
Ótima resposta! Acabei de adicionar uma "solução" para fazer este trabalho também com textareas bem - gist.github.com/rmontgomery429/6191275
Ryan Montgomery
por que você definiu controllerpara sua diretiva e por que não usou o linkmétodo? Eu sou novato para AngularJS
ebram khalil
1
necessário mudar val = $attrs.ngInitial || $attrs.value || $element.val()para que ele funcione com elementos selecionados.
fjsj
12

IMHO, a melhor solução é a diretiva @Kevin Stone, mas tive que atualizá-la para funcionar em todas as condições (fe select, textarea), e esta está funcionando com certeza:

    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
fonte
E quanto a selecionar?
jwize
7

Você pode usar uma diretiva personalizada (com suporte para área de texto, seleção, rádio e caixa de seleção), confira esta postagem no blog https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form- campos-atributos / .

user1519240
fonte
1
Este é um ótimo post. Minha resposta favorita desde a pergunta era definir o valor de uma entrada de acordo com os valores iniciais.
amigos estão dizendo sobre rp deshaies
Criei um Plnkr para testar esse código e ele funciona muito bem: plnkr.co/edit/ZTFOAc2ZGIZr6HjsB5gH?p=preview
RPDeshaies
6

Você também pode usar dentro do seu código HTML: ng-init="card.description = 12345"

Não é recomendado pela Angular e, como mencionado acima, você deve usar exclusivamente seu controlador.

Mas funciona :)

koxon
fonte
4

Eu tenho uma abordagem simples, porque tenho algumas validações e máscaras pesadas em meus formulários. Então, eu usei o jquery para obter meu valor novamente e acionar o evento "change" nas validações:

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

Como outros apontaram, não é uma boa prática inicializar dados nas visualizações. A inicialização dos dados nos controladores, no entanto, é recomendada. (consulte http://docs.angularjs.org/guide/controller )

Então você pode escrever

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

e

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

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

Dessa forma, as visualizações não contêm dados e o controlador inicializa o valor enquanto os valores reais estão sendo carregados.

Jkarttunen
fonte
3

Se você gosta da abordagem de Kevin Stone acima, https://stackoverflow.com/a/17823590/584761, considere uma abordagem mais fácil escrevendo diretivas para tags 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);
            }
        }
    }; });

Se você seguir esse caminho, não precisará se preocupar em adicionar ng-initial a cada tag. Ele define automaticamente o valor do modelo para o atributo value da tag. Se você não definir o atributo value, o padrão será uma sequência vazia.

codificador
fonte
3

Aqui está uma abordagem centrada no 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>

É a mesma ideia básica que as respostas mais populares sobre esta questão, exceto que $ allow.value registra um serviço que contém seus valores padrão.

Portanto, no servidor, você pode ter algo como:

{
    description: "Visa-4242"
}

E coloque-o na sua página através da tecnologia do servidor de sua escolha. Aqui está uma lista: https://gist.github.com/exclsr/c8c391d16319b2d31a43

exclsr
fonte
1
Até onde eu sei, esse é o método mais angular para lidar com o problema se fazer uma solicitação inicial de $ http não for uma opção. Ele também tem o benefício de ser muito mais fácil de modificar, se você desejar trocar para $ http posteriormente. Um possível aprimoramento é o retorno de uma promessa para facilitar ainda mais a troca. Eu evitaria definir vars globais ou usar o ng-initial aqui.
Richard
1

Esta é uma versão mais genérica das idéias mencionadas acima ... Simplesmente verifica se existe algum valor no modelo e, se não, define o valor para o 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 (exemplo 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
fonte
Isso funcionou para mim, mas somente depois de passar $scopepara a chamada para getter()- espero que isso esclareça as coisas para qualquer pessoa que tente fazer isso!
zesda
0

Eu tentei o que @Mark Rajcok sugeriu. Está trabalhando para valores String (Visa-4242). Por favor, consulte este violino .

Do violino:

A mesma coisa que é feita no violino pode ser feita usando ng-repeat, que todo mundo poderia recomendar. Mas depois de ler a resposta dada por Mark Rajcok, eu só queria tentar o mesmo para um formulário com uma variedade de perfis. As coisas funcionam bem até que eu tenha o $ scope.profiles = [{}, {}]; código no controlador. Se eu remover esse código, estou recebendo erros. Mas em cenários normais, não consigo imprimir $scope.profiles = [{},{}]; como imprimo ou ecoo html no servidor. Será possível executar o acima, de maneira semelhante ao @Mark Rajcok para os valores de sequência como <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, sem ter que repetir a parte JavaScript do servidor.

Rajkamal Subramanian
fonte
1
É possível utilizar ng-init para inicializar a matriz e, em seguida, utilizar-ng de repetição para a saída das linhas de forma: jsfiddle.net/6tP6x/1
Karen Zilles
0

Acabei de adicionar suporte para o elemento select a Ryan Montgomery "consertar"

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

Se você possui o valor init na URL mypage/id, então, no controlador da JS angular, você pode usar location.pathnamepara encontrar o ID e atribuí-lo ao modelo que deseja.

Néon
fonte