Eu tenho um serviço, diga:
factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
var service = {
foo: []
};
return service;
}]);
E eu gostaria de usar foo
para controlar uma lista que é renderizada em HTML:
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Para que o controlador detecte quando aService.foo
é atualizado, juntei esse padrão em que adiciono aService ao controlador $scope
e depois uso $scope.$watch()
:
function FooCtrl($scope, aService) {
$scope.aService = aService;
$scope.foo = aService.foo;
$scope.$watch('aService.foo', function (newVal, oldVal, scope) {
if(newVal) {
scope.foo = newVal;
}
});
}
Isso parece demorado, e eu tenho repetido isso em todos os controladores que usam as variáveis do serviço. Existe uma maneira melhor de realizar a observação de variáveis compartilhadas?
angularjs
watch
angular-services
berto
fonte
fonte
aService.foo
na marcação html? (assim: plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )Respostas:
Você sempre pode usar o bom e velho padrão de observador se quiser evitar a tirania e a sobrecarga
$watch
.No serviço:
E no controlador:
fonte
$destory
evento sobre o alcance e adicione um método de cancelar o registro paraaService
$watch
O padrão de observador vs é simplesmente escolher entre pesquisar ou enviar e é basicamente uma questão de desempenho; portanto, use-o quando o desempenho for importante. Uso o padrão de observador quando, caso contrário, teria que "aprofundar" observar objetos complexos. Anexo serviços inteiros ao escopo $ em vez de observar valores de serviço únicos. Eu evito o $ watch da angular como o diabo, isso acontece o suficiente nas diretivas e na ligação de dados angulares nativa.Em um cenário como este, em que vários objetos desconhecidos podem estar interessados em alterações, use
$rootScope.$broadcast
partir do item que está sendo alterado.Em vez de criar seu próprio registro de ouvintes (que precisam ser limpos com várias destruições de $), você deve poder
$broadcast
serviço em questão.Você ainda deve codificar os
$on
manipuladores em cada ouvinte, mas o padrão é dissociado de várias chamadas para$digest
e, assim, evita o risco de observadores de longa duração.Dessa forma, também, os ouvintes podem ir e vir do DOM e / ou escopos filho diferentes sem que o serviço altere seu comportamento.
** atualização: exemplos **
As transmissões fazem mais sentido nos serviços "globais" que podem afetar inúmeras outras coisas no seu aplicativo. Um bom exemplo é um serviço de usuário, no qual existem vários eventos que podem ocorrer, como logon, logout, atualização, ocioso etc. Eu acredito que é aqui que as transmissões fazem mais sentido porque qualquer escopo pode escutar um evento, sem mesmo injetando o serviço, e ele não precisa avaliar nenhuma expressão ou resultados de cache para inspecionar alterações. Ele é acionado e esquecido (por isso, verifique se é uma notificação de acionar e esquecer, não algo que exija ação)
O serviço acima transmitia uma mensagem para todos os escopos quando a função save () era concluída e os dados eram válidos. Como alternativa, se for um recurso $ ou um envio de ajax, mova a chamada de transmissão para o retorno de chamada para que seja acionada quando o servidor responder. As transmissões se adequam muito bem a esse padrão, porque todo ouvinte apenas espera o evento sem a necessidade de inspecionar o escopo em cada $ digest. O ouvinte ficaria assim:
Um dos benefícios disso é a eliminação de vários relógios. Se você estivesse combinando campos ou obtendo valores como o exemplo acima, seria necessário observar as propriedades firstname e lastname. Observar a função getUser () funcionaria apenas se o objeto do usuário fosse substituído nas atualizações, não seria acionado se o objeto do usuário apenas tivesse suas propriedades atualizadas. Nesse caso, você teria que fazer uma inspeção profunda e isso é mais intenso.
$ broadcast envia a mensagem do escopo em que ela é chamada para qualquer escopo filho. Portanto, chamá-lo de $ rootScope será acionado em todos os escopos. Se você fosse $ broadcast do escopo do seu controlador, por exemplo, ele seria acionado apenas nos escopos herdados do escopo do seu controlador. $ emit vai na direção oposta e se comporta de maneira semelhante a um evento DOM, na medida em que borbulha a cadeia de escopo.
Lembre-se de que há cenários em que $ broadcast faz muito sentido e há cenários em que $ watch é uma opção melhor - especialmente se em um escopo isolado com uma expressão de observação muito específica.
fonte
Estou usando uma abordagem semelhante à @dtheodot, mas usando promessa angular em vez de passar retornos de chamada
Então, sempre que usar
myService.setFoo(foo)
, use o método para atualizar ofoo
serviço. No seu controlador, você pode usá-lo como:Os dois primeiros argumentos de
then
são retornos de chamada de sucesso e erro, o terceiro é notificar retorno de chamada.Referência para $ q.
fonte
$scope.$watch
uma variável de serviço não parecia funcionar (o escopo que eu estava assistindo era um modal herdado de$rootScope
) - isso funcionou. Truque legal, obrigado por compartilhar!Sem relógios ou retornos de chamada do observador ( http://jsfiddle.net/zymotik/853wvv7s/ ):
JavaScript:
HTML
Esse JavaScript funciona quando estamos devolvendo um objeto do serviço, e não um valor. Quando um objeto JavaScript é retornado de um serviço, o Angular adiciona relógios a todas as suas propriedades.
Observe também que estou usando 'var self = this', pois preciso manter uma referência ao objeto original quando o tempo limite $ for executado, caso contrário, 'this' se referirá ao objeto da janela.
fonte
$scope.count = service.count
não funciona.$scope.data = service.data
<p>Count: {{ data.count }}</p>
Eu me deparei com essa pergunta procurando algo semelhante, mas acho que ela merece uma explicação completa do que está acontecendo, além de algumas soluções adicionais.
Quando uma expressão angular como a que você usou está presente no HTML, o Angular configura automaticamente um
$watch
para$scope.foo
e atualiza o HTML sempre$scope.foo
que for alterado.O problema não dito aqui é que uma das duas coisas está afetando, de
aService.foo
modo que as alterações não são detectadas. Essas duas possibilidades são:aService.foo
está sendo definido para uma nova matriz a cada vez, fazendo com que a referência a ela fique desatualizada.aService.foo
está sendo atualizado de forma que um$digest
ciclo não seja acionado na atualização.Problema 1: referências desatualizadas
Considerando a primeira possibilidade, assumindo que a
$digest
está sendo aplicada, seaService.foo
sempre foi a mesma matriz, o conjunto automaticamente$watch
detectaria as alterações, conforme mostrado no snippet de código abaixo.Solução 1-a: verifique se a matriz ou o objeto é o mesmo objeto em cada atualização
Mostrar snippet de código
Como você pode ver, o ng-repeat supostamente anexado ao
aService.foo
não é atualizado quandoaService.foo
muda, mas o ng-repeat anexado aoaService2.foo
faz . Isso ocorre porque nossa referência aaService.foo
está desatualizada, mas nossa referência aaService2.foo
não é. Criamos uma referência à matriz inicial com$scope.foo = aService.foo;
, que foi descartada pelo serviço na próxima atualização, o que significa$scope.foo
não faz mais referência à matriz que queríamos mais.No entanto, embora existam várias maneiras de garantir que a referência inicial seja mantida intacta, às vezes pode ser necessário alterar o objeto ou a matriz. Ou, e se a propriedade service fizer referência a um primitivo como a
String
ouNumber
? Nesses casos, não podemos simplesmente confiar em uma referência. Então, o que pode fazer?Várias das respostas dadas anteriormente já fornecem algumas soluções para esse problema. No entanto, sou pessoalmente a favor do uso do método simples sugerido por Jin e nas semanas seguintes nos comentários:
Solução 1-b: anexe o serviço ao escopo e faça referência
{service}.{property}
no HTML.Significado, basta fazer o seguinte:
HTML:
JS:
Mostrar snippet de código
Dessa forma, a
$watch
resolução será resolvidaaService.foo
em cada$digest
, obtendo o valor atualizado corretamente.Isso é o que você estava tentando fazer com a solução alternativa, mas de uma maneira muito menos complicada. Você adicionou uma desnecessária
$watch
no controlador que coloca explicitamentefoo
na$scope
sempre que muda. Você não precisa desse extra$watch
quando anexar emaService
vez deaService.foo
ao$scope
e vincular explicitamente àaService.foo
marcação.Agora está tudo bem desde que um
$digest
ciclo esteja sendo aplicado. Nos meus exemplos acima, usei o$interval
serviço da Angular para atualizar as matrizes, que inicia automaticamente um$digest
loop após cada atualização. Mas e se as variáveis de serviço (por qualquer motivo) não estiverem sendo atualizadas dentro do "mundo Angular". Em outras palavras, não temos um$digest
ciclo sendo ativado automaticamente sempre que a propriedade do serviço muda?Problema 2: ausente
$digest
Muitas das soluções aqui resolverão esse problema, mas eu concordo com o Code Whisperer :
Portanto, eu preferiria continuar usando o
aService.foo
referência na marcação HTML, como mostrado no segundo exemplo acima, e não precisar registrar um retorno de chamada adicional no Controller.Solução 2: use um levantador e um levantador com
$rootScope.$apply()
Fiquei surpreso que ninguém ainda tenha sugerido o uso de um setter e getter . Esse recurso foi introduzido no ECMAScript5 e, portanto, existe há anos. Claro, isso significa que, por qualquer motivo, você precisa oferecer suporte a navegadores realmente antigos, esse método não funcionará, mas eu sinto que getters e setters são muito subutilizados no JavaScript. Nesse caso em particular, eles podem ser bastante úteis:
Mostrar snippet de código
Aqui eu adicionei uma variável 'privada' na função de serviço:
realFoo
. Este get é atualizado e recuperado usando as funçõesget foo()
eset foo()
respectivamente noservice
objeto.Observe o uso de
$rootScope.$apply()
na função definida. Isso garante que o Angular esteja ciente de quaisquer alterações noservice.foo
. Se você receber erros 'inprog', consulte esta página de referência útil ou se você usa Angular> = 1.3, pode simplesmente usar$rootScope.$applyAsync()
.Também tenha cuidado com isso, se
aService.foo
estiver sendo atualizado com muita frequência, pois isso pode afetar significativamente o desempenho. Se o desempenho for um problema, você pode configurar um padrão de observador semelhante às outras respostas aqui usando o configurador.fonte
services
não propriedades que pertencem ao próprio serviço.Até onde eu sei, você não precisa fazer algo tão elaborado quanto isso. Você já atribuiu foo do serviço ao seu escopo e, como foo é uma matriz (e, por sua vez, um objeto, ele é atribuído por referência!). Então, tudo o que você precisa fazer é algo como isto:
Se alguma outra variável nesse mesmo Ctrl depende da mudança de foo, então sim, você precisaria de um relógio para observar foo e fazer alterações nessa variável. Mas desde que seja uma simples referência, a observação é desnecessária. Espero que isto ajude.
fonte
$watch
trabalhar com um primitivo. Em vez disso, eu definido um método do serviço que iria devolver o valor primitivo:somePrimitive() = function() { return somePrimitive }
E eu atribuída uma propriedade $ escopo para esse método:$scope.somePrimitive = aService.somePrimitive;
. Então eu usei o método âmbito do HTML:<span>{{somePrimitive()}}</span>
$scope.foo = aService.foo
não atualiza a variável de escopo automaticamente.Você pode inserir o serviço no $ rootScope e assistir:
No seu controlador:
Outra opção é usar uma biblioteca externa como: https://github.com/melanke/Watch.JS
Funciona com: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rhino 1.7+
Você pode observar as alterações de um, muitos ou todos os atributos do objeto.
Exemplo:
fonte
Você pode assistir as alterações dentro da própria fábrica e depois transmitir uma alteração
Então no seu controlador:
Dessa maneira, você coloca todo o código de fábrica relacionado em sua descrição e só pode confiar na transmissão externa
fonte
== ATUALIZADO ==
Muito simples agora em $ watch.
Caneta aqui .
HTML:
JavaScript:
fonte
Com base na resposta do dtheodor, você pode usar algo semelhante ao abaixo para garantir que você não esqueça de cancelar o registro do retorno de chamada ...
$scope
Porém, alguns podem se opor a passar o para um serviço.Array.remove é um método de extensão que se parece com isso:
fonte
Aqui está minha abordagem genérica.
No seu controlador
fonte
Para aqueles como eu, apenas procurando uma solução simples, isso faz quase exatamente o que você espera do uso normal de $ watch nos controladores. A única diferença é que ele avalia a string no seu contexto javascript e não em um escopo específico. Você precisará injetar $ rootScope em seu serviço, embora ele seja usado apenas para se conectar aos ciclos de compilação corretamente.
fonte
enquanto enfrentava um problema muito semelhante, assisti a uma função no escopo e a função retornou a variável de serviço. Eu criei um violino js . você pode encontrar o código abaixo.
fonte
Eu vim para essa pergunta, mas o problema foi que eu estava usando setInterval quando deveria estar usando o provedor de intervalo angular $. Este também é o caso de setTimeout (use $ timeout). Sei que não é a resposta para a pergunta do OP, mas pode ajudar alguns, como me ajudou.
fonte
setTimeout
, ou qualquer outra função não-Angular, mas não se esqueça de incluir o código no retorno de chamada$scope.$apply()
.Eu encontrei uma solução realmente ótima no outro thread com um problema semelhante, mas uma abordagem totalmente diferente. Fonte: AngularJS: $ watch dentro da diretiva não está funcionando quando o valor $ rootScope é alterado
Basicamente, a solução não diz para NÃO usar
$watch
, pois é uma solução muito pesada. Em vez disso, eles propõem usar$emit
e$on
.Meu problema era assistir uma variável em meu serviço e reagir na diretiva . E com o método acima, é muito fácil!
Meu exemplo de módulo / serviço:
Então, basicamente, assisto meu
user
- sempre que definido como novo valor,$emit
umuser:change
status.Agora, no meu caso, na diretiva eu usei:
Agora na directiva eu escuto na
$rootScope
e sobre a mudança dada - reajo respectivamente. Muito fácil e elegante!fonte
// service: (nada de especial aqui)
// ctrl:
fonte
Um pouco feio, mas adicionei o registro de variáveis de escopo ao meu serviço para alternar:
E então no controlador:
fonte
this
desse serviço em vez deself
?return this;
fora de seus construtores ;-)Dê uma olhada neste plunker :: este é o exemplo mais simples que eu poderia pensar
http://jsfiddle.net/HEdJF/
fonte
Eu vi alguns padrões terríveis de observadores aqui que causam vazamentos de memória em aplicativos grandes.
Eu posso me atrasar um pouco, mas é tão simples assim.
A função watch observa as alterações de referência (tipos primitivos) se você quiser assistir algo como push de matriz, basta usar:
someArray.push(someObj); someArray = someArray.splice(0);
Isso atualizará a referência e o relógio de qualquer lugar. Incluindo um método de obtenção de serviços. Qualquer coisa que seja primitiva será atualizada automaticamente.
fonte
Estou atrasado para a parte, mas achei uma maneira melhor de fazer isso do que a resposta postada acima. Em vez de atribuir uma variável para armazenar o valor da variável de serviço, criei uma função anexada ao escopo, que retorna a variável de serviço.
controlador
Eu acho que isso fará o que você quiser. Meu controlador continua verificando o valor do meu serviço com esta implementação. Honestamente, isso é muito mais simples que a resposta selecionada.
fonte
Escrevi dois serviços utilitários simples que me ajudam a rastrear alterações nas propriedades do serviço.
Se você quiser pular a longa explicação, pode ir direto ao jsfiddle
Este serviço é usado no controlador da seguinte maneira: Suponha que você tenha um serviço "testService" com as propriedades 'prop1', 'prop2', 'prop3'. Você deseja assistir e atribuir ao escopo 'prop1' e 'prop2'. Com o serviço de relógio, ficará assim:
Eu o acionaria no final do meu código assíncrono para acionar o loop $ digest. Curtiu isso:
Então, tudo isso ficará assim (você pode executá-lo ou abrir o violino ):
fonte