Como “desbloquear” uma expressão

Digamos que eu tenha uma repetição com uma grande matriz.

Quando ng-repeat é executado, ele adiciona todos os elementos desse array a um escopo isolado, além de ter o próprio array em um escopo. Isso significa que $ digest verifica a matriz inteira em busca de alterações e, além disso, verifica todos os elementos individuais dessa matriz em busca de alterações.

Veja este plunker como um exemplo do que estou falando.

No meu caso de uso, nunca mudo um único elemento da minha matriz para não precisar assisti-los. Eu só mudarei o array inteiro, nesse caso, o ng-repeat renderizaria novamente a tabela em sua totalidade. (Se eu estiver errado sobre isso, por favor me avise ..)

Em uma matriz de (digamos) 1000 linhas, são mais 1000 expressões que não precisam ser avaliadas.

Como posso cancelar o registro de cada elemento do observador enquanto ainda assisto à matriz principal?

Talvez em vez de cancelar o registro eu pudesse ter mais controle do meu $ digest e de alguma forma pular cada linha individual?

Este caso específico é na verdade um exemplo de um problema mais geral. Eu sei que $ watch retorna uma function de ‘cancelamento de registro’ , mas isso não ajuda quando uma diretiva está registrando os relógios, o que é na maioria das vezes.

Para ter um repetidor com uma grande matriz que você não assiste para assistir a todos os itens.

Você precisará criar uma diretiva personalizada que leve um argumento e uma expressão para sua matriz, depois, na function de vinculação, você observaria essa matriz e faria com que a function de vinculação atualizasse programaticamente o HTML (em vez de usar ng-repeat)

algo como (código psuedo):

app.directive('leanRepeat', function() { return { restrict: 'E', scope: { 'data' : '=' }, link: function(scope, elem, attr) { scope.$watch('data', function(value) { elem.empty(); //assuming jquery here. angular.forEach(scope.data, function(d) { //write it however you're going to write it out here. elem.append('
' + d + '
'); }); }); } }; });

… o que parece ser uma dor na bunda.

Método alternativo hackish

Você pode fazer um loop pelos $scope.$$watchers e examinar $scope.$$watchers[0].exp.exp para ver se ele corresponde à expressão que você deseja remover e, em seguida, removê-lo com uma splice() simples splice() ligue. O PITA aqui, é que coisas como Blah {{whatever}} Blah entre tags serão a expressão e includeão até retornos de carro.

No lado positivo, você pode apenas fazer um loop através do $ escopo de seu ng-repeat e apenas remover tudo, então adicionar explicitamente o relógio que você quer … Eu não sei.

De qualquer maneira, parece um hack.

Para remover um observador feito pelo $ scope. $ Watch

Você pode cancelar o registro de um $watch com a function retornada pela chamada $watch :

Por exemplo, para ter um $watch somente de $watch uma vez:

 var unregister = $scope.$watch('whatever', function(){ alert('once!'); unregister(); }); 

Você pode, claro, chamar a function de cancelamento de registro sempre que quiser … isso foi apenas um exemplo.

Conclusão: Não há realmente uma ótima maneira de fazer exatamente o que você está pedindo

Mas uma coisa a considerar: vale a pena se preocupar? Além disso, é realmente uma boa ideia ter milhares de registros carregados em dezenas de DOMElements cada um? Alimento para o pensamento.

Espero que isso ajude.


EDIT 2 (removido idéia ruim)

$ watch retorna uma function que desassocia o $ watch quando chamado. Então, isso é tudo que você precisa para “watchOnce”:

 var unwatchValue = scope.$watch('value', function(newValue, oldValue) { // Do your thing unwatchValue(); }); 

Editar: veja a outra resposta que eu postei.

Eu fui e implementei a ideia do blesh de uma maneira separável. Minha diretiva ngOnce apenas destrói o escopo filho que ngRepeat cria em cada item. Isso significa que o escopo não é alcançado a partir do scope.$digest de seus pais, scope.$digest e os observadores nunca são executados.

Fonte e exemplo no JSFiddle

A diretiva em si:

 angular.module('transclude', []) .directive('ngOnce', ['$timeout', function($timeout){ return { restrict: 'EA', priority: 500, transclude: true, template: '
', compile: function (tElement, tAttrs, transclude) { return function postLink(scope, iElement, iAttrs, controller) { $timeout(scope.$destroy.bind(scope), 0); } } }; }]);

Usando isso:

  
  • {{item.title}}: {{item.text}}
  • Nota ng-once não cria seu próprio escopo, o que significa que ele pode afetar os elementos irmãos. Todos estes fazem a mesma coisa:

      
  • {{item.title}}: {{item.text}}
  • {{item.title}}: {{item.text}}
  • {{item.title}}: {{item.text}}
  • Note que isso pode ser uma má ideia

    Você pode adicionar a diretiva bindonce ao seu ng-repeat . Você precisará fazer o download em https://github.com/pasvaz/bindonce .

    edit : algumas ressalvas:

    Se você estiver usando a interpolação {{}} no seu modelo, precisará substituí-lo por .

    Se você estiver usando ng- diretivas, você precisa substituí-las com as diretivas bo- direito.

    Além disso, se você está colocando bindonce e ng-repeat no mesmo elemento, você deve tentar mover o bindonce para um elemento pai (veja https://github.com/Pasvaz/bindonce/issues/25#issuecomment-25457970 ) ou adicionando track by para o seu ng-repeat .

    Se você estiver usando o angularjs 1.3 ou superior, você pode usar a syntax de binding única como

     
  • {{item}}
  • Isso ligará o valor e removerá os observadores assim que o primeiro ciclo de digitação for executado e o valor for alterado de undefined para defined pela primeira vez.

    Um BLOG muito útil sobre isso.