Eu tenho um serviço AngularJS que desejo inicializar com alguns dados assíncronos. Algo assim:
myModule.service('MyService', function($http) {
var myData = null;
$http.get('data.json').success(function (data) {
myData = data;
});
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Obviamente, isso não funcionará porque se algo tentar chamar doStuff()
antes de myData
voltar, receberei uma exceção de ponteiro nulo. Pelo que sei ao ler algumas das outras perguntas feitas aqui e aqui , tenho algumas opções, mas nenhuma delas parece muito limpa (talvez esteja faltando alguma coisa):
Serviço de instalação com "executar"
Ao configurar meu aplicativo, faça o seguinte:
myApp.run(function ($http, MyService) {
$http.get('data.json').success(function (data) {
MyService.setData(data);
});
});
Então, meu serviço ficaria assim:
myModule.service('MyService', function() {
var myData = null;
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Isso funciona algumas vezes, mas se os dados assíncronos levarem mais tempo do que o necessário para que tudo seja inicializado, recebo uma exceção de ponteiro nulo quando ligo doStuff()
Use objetos de promessa
Provavelmente isso funcionaria. A única desvantagem em todos os lugares que eu chamo de MyService, terei que saber que doStuff () retorna uma promessa e todo o código precisará de nós then
para interagir com a promessa. Prefiro esperar até que myData esteja de volta antes de carregar o meu aplicativo.
Bootstrap manual
angular.element(document).ready(function() {
$.getJSON("data.json", function (data) {
// can't initialize the data here because the service doesn't exist yet
angular.bootstrap(document);
// too late to initialize here because something may have already
// tried to call doStuff() and would have got a null pointer exception
});
});
Global de Javascript Var eu poderia enviar meu JSON diretamente para uma variável global Javascript:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
Em seguida, ele estará disponível ao inicializar MyService
:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
Isso funcionaria também, mas então eu tenho uma variável javascript global que cheira mal.
Essas são minhas únicas opções? Uma dessas opções é melhor que as outras? Sei que essa é uma pergunta bastante longa, mas queria mostrar que tentei explorar todas as minhas opções. Qualquer orientação seria muito apreciada.
fonte
$http
, salvar dados em um serviço e inicializar um aplicativo.Respostas:
Você já deu uma olhada
$routeProvider.when('/path',{ resolve:{...}
? Pode tornar a promessa um pouco mais limpa:Exponha uma promessa em seu serviço:
Adicione
resolve
à sua configuração de rota:Seu controlador não será instanciado antes que todas as dependências sejam resolvidas:
Fiz um exemplo em plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
fonte
resolve
propriedade a um controlador que não é mencionado em$routeProvider
. Por exemplo<div ng-controller="IndexCtrl"></div>
,. Aqui, o controlador é mencionado explicitamente e não é carregado via roteamento. Nesse caso, como atrasaria a instanciação do controlador?Com base na solução de Martin Atkins, aqui está uma solução completa e concisa de ângulo puro:
Essa solução usa uma função anônima de execução automática para obter o serviço $ http, solicitar a configuração e injetar em uma constante chamada CONFIG quando estiver disponível.
Uma vez completamente, aguardamos até que o documento esteja pronto e inicializamos o aplicativo Angular.
Esse é um pequeno aprimoramento da solução de Martin, que adiou a busca da configuração até o final do documento. Tanto quanto eu sei, não há razão para adiar a chamada de $ http para isso.
Teste de Unidade
Nota: Descobri que esta solução não funciona bem ao testar a unidade quando o código está incluído no seu
app.js
arquivo. A razão para isso é que o código acima é executado imediatamente quando o arquivo JS é carregado. Isso significa que a estrutura de teste (Jasmine no meu caso) não tem chance de fornecer uma implementação simulada de$http
.Minha solução, com a qual não estou completamente satisfeito, foi mover esse código para o nosso
index.html
arquivo, para que a infraestrutura de teste da unidade Grunt / Karma / Jasmine não o veja.fonte
Usei uma abordagem semelhante à descrita por @XMLilley, mas queria ter a capacidade de usar os serviços AngularJS, como
$http
carregar a configuração e fazer uma inicialização adicional sem o uso de APIs de baixo nível ou jQuery.O uso
resolve
de rotas também não era uma opção, porque eu precisava que os valores estivessem disponíveis como constantes quando meu aplicativo for iniciado, mesmo emmodule.config()
blocos.Criei um pequeno aplicativo AngularJS que carrega a configuração, os define como constantes no aplicativo real e o inicializa.
Veja-o em ação (usando em
$timeout
vez de$http
) aqui: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewATUALIZAR
Eu recomendaria usar a abordagem descrita abaixo por Martin Atkins e JBCP.
ATUALIZAÇÃO 2
Como eu precisava dele em vários projetos, acabei de lançar um módulo bower que cuida disso: https://github.com/philippd/angular-deferred-bootstrap
Exemplo que carrega dados do back-end e define uma constante chamada APP_CONFIG no módulo AngularJS:
fonte
O caso "inicialização manual" pode obter acesso aos serviços Angular criando manualmente um injetor antes da inicialização. Esse injetor inicial permanecerá sozinho (não será anexado a nenhum elemento) e incluirá apenas um subconjunto dos módulos carregados. Se tudo o que você precisa são serviços Angular básicos, basta carregar
ng
, desta forma:Você pode, por exemplo, usar o
module.constant
mecanismo para disponibilizar dados para o seu aplicativo:myAppConfig
Agora isso pode ser injetado como qualquer outro serviço e, em particular, está disponível durante a fase de configuração:ou, para um aplicativo menor, você pode simplesmente injetar a configuração global diretamente no seu serviço, à custa de espalhar conhecimento sobre o formato de configuração em todo o aplicativo.
Obviamente, como as operações assíncronas aqui bloquearão a inicialização do aplicativo e, assim, a compilação / vinculação do modelo, é aconselhável usar a
ng-cloak
diretiva para impedir que o modelo não analisado apareça durante o trabalho. Você também pode fornecer algum tipo de indicação de carregamento no DOM, fornecendo algum HTML que é mostrado apenas até a inicialização do AngularJS:Criei um exemplo completo e funcional dessa abordagem no Plunker, carregando a configuração de um arquivo JSON estático como exemplo.
fonte
Eu tive o mesmo problema: eu amo o
resolve
objeto, mas isso só funciona para o conteúdo da ng-view. E se você tiver controladores (para navegação de nível superior, digamos) que existem fora do ng-view e que precisam ser inicializados com dados antes que o roteamento comece a acontecer? Como evitamos mexer no lado do servidor apenas para fazer esse trabalho?Use a inicialização manual e uma constante angular . Um XHR ingênuo obtém seus dados e você inicializa angular no retorno de chamada, o que lida com os problemas de async. No exemplo abaixo, você nem precisa criar uma variável global. Os dados retornados existem apenas no escopo angular como injetáveis e nem estão presentes dentro de controladores, serviços etc., a menos que você os injete. (Por mais que você injete a saída do seu
resolve
objeto no controlador para obter uma visão roteada.) Se você preferir interagir posteriormente com esses dados como um serviço, poderá criar um serviço, injetar os dados e ninguém será mais sábio. .Exemplo:
Agora, sua
NavData
constante existe. Vá em frente e injete-o em um controlador ou serviço:Obviamente, o uso de um objeto XHR simples remove uma série de detalhes que o
$http
JQuery cuidaria de você, mas este exemplo funciona sem dependências especiais, pelo menos para uma simplesget
. Se você deseja um pouco mais de energia para sua solicitação, carregue uma biblioteca externa para ajudá-lo. Mas não acho que seja possível acessar$http
ferramentas angulares ou outras neste contexto.( Postagem relacionada ao SO )
fonte
O que você pode fazer é no seu .config para o aplicativo, criar o objeto de resolução para a rota e, na função, passar em $ q (objeto da promessa) e o nome do serviço do qual você está dependendo, e resolver a promessa no função de retorno de chamada para o $ http no serviço da seguinte forma:
CONFIGURAÇÃO DE ROTA
Angular não renderizará o modelo nem disponibilizará o controlador até que defer.resolve () seja chamado. Podemos fazer isso em nosso serviço:
SERVIÇO
Agora que o MyService possui os dados atribuídos a sua propriedade de dados e a promessa no objeto de resolução de rota foi resolvida, nosso controlador para a rota entra em ação e podemos atribuir os dados do serviço ao objeto de controlador.
CONTROLADOR
Agora toda a nossa ligação no escopo do controlador poderá usar os dados originados no MyService.
fonte
resolve
um objeto com uma propriedade sendo a função. por isso acabou sendoresolve:{ dataFetch: function(){ // call function here } }
Então eu encontrei uma solução. Criei um serviço angularJS, vamos chamá-lo de MyDataRepository e criei um módulo para ele. Em seguida, sirvo esse arquivo javascript do meu controlador do lado do servidor:
HTML:
Lado do servidor:
Posso injetar MyDataRepository sempre que precisar:
Isso funcionou muito bem para mim, mas estou aberto a qualquer feedback, se alguém tiver algum. }
fonte
Além disso, você pode usar as seguintes técnicas para provisionar seu serviço globalmente, antes da execução dos controladores reais: https://stackoverflow.com/a/27050497/1056679 . Basta resolver seus dados globalmente e depois passá-los para o seu serviço em
run
bloco, por exemplo.fonte
Você pode usar
JSONP
para carregar dados de serviço de forma assíncrona. A solicitação JSONP será feita durante o carregamento da página inicial e os resultados estarão disponíveis antes do início do aplicativo. Dessa forma, você não precisará inchar seu roteamento com resoluções redundantes.Você html ficaria assim:
fonte
A maneira mais fácil de buscar qualquer diretório inicializado é usar o ng-init.
Basta colocar o escopo ng-init div onde você deseja buscar dados init
index.html
index.js
fonte