AngularJS + JQuery: como fazer com que o conteúdo dinâmico funcione no angularjs

84

Estou trabalhando em um aplicativo Ajax usando jQuery e AngularJS.

Quando eu atualizo o conteúdo (que contém ligações AngularJS) de um div usando a htmlfunção jQuery , as ligações AngularJS não funcionam.

A seguir está o código do que estou tentando fazer:

$(document).ready(function() {
  $("#refreshButton").click(function() {
    $("#dynamicContent").html("<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>")
  });
});
</style><script src="http://docs.angularjs.org/angular-1.0.1.min.js"></script><style>.ng-invalid {
  border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="">
  <div id='dynamicContent'>
    <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
    <span>count: {{count}} </span>
  </div>


  <button id='refreshButton'>
    Refresh
  </button>
</div>

Eu tenho conteúdo dinâmico dentro de um div com o ID #dynamicContente tenho um botão de atualização que atualizaria o conteúdo deste div quando atualizar fosse clicado. O incremento funciona conforme o esperado se eu não atualizar o conteúdo, mas depois de atualizar, a vinculação do AngularJS para de funcionar.

Isso pode não ser válido no AngularJS, mas inicialmente desenvolvi o aplicativo com jQuery e comecei a usar o AngularJS mais tarde, então não posso migrar tudo para o AngularJS. Qualquer ajuda para fazer isso funcionar no AngularJS é muito apreciada.

Rajá
fonte
1
Existe algum motivo específico para usar JQuery para essa funcionalidade? Como isso é bem e facilmente coberto pelo angular: < jsfiddle.net/pkozlowski_opensource/YCrFD/2 >
pkozlowski.opensource
3
Esta é apenas uma versão simplificada do meu caso de uso real para mostrar o problema. Na aplicação real, o conteúdo dinâmico é gerado por grails taglib, que é passado para jquery como html. Portanto, não posso transferir toda a lógica do taglib do grails para o angularjs para torná-lo puro angularjs.
Raja
1
@ pkozlowski.opensource, esse link está morto? Além disso, você deve participar do outdoors.stackexchange.com :)
Liam

Respostas:

115

Você precisa chamar $compilea string HTML antes de inseri-la no DOM para que o angular tenha a chance de realizar a vinculação.

Em seu violino, seria algo assim.

$("#dynamicContent").html(
  $compile(
    "<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>"
  )(scope)
);

Obviamente, $compiledeve ser injetado em seu controlador para que funcione.

Leia mais na $compiledocumentação .

Noah Freitas
fonte
2
Obrigado Noah, compilar está funcionando quando eu faço isso em um método de controlador e vinculo diretamente esse método de controlador ao evento de clique do botão. Aqui está um exemplo de trabalho . Mas no meu aplicativo real, tive que chamar o método do controlador de uma função jquery diferente. então peguei a referência ao objeto de escopo e chamei o método de atualização para recompilar, o que não funciona. Aqui está um exemplo . Você pode ajudar a fazer com que este exemplo funcione.
Raja
1
@Raja Quando você chama métodos / expressões angulares fora da estrutura angular, você deve chamar $ scope. $ Apply () para executar o ciclo de vida de escopo adequado de tratamento de exceções, execução de relógios, etc. jsfiddle.net/fABdD/14
Artem Andreev
1
@ArtemAndreev exatamente certo. @Raja você também pode mover a chamada para $scope.$apply()na refresh()própria função se fizer sentido para sua arquitetura: jsfiddle.net/nfreitas/8xkeh/1
Noah Freitas
3
O link para angular-1.0.1.min.jsnos exemplos originais era 404. Aqui estão as versões de trabalho atualizadas do exemplo de @ ArtemAndreev-s: jsfiddle.net/pmorch/fABdD/186 e @NoahFreitas exemplo: jsfiddle.net/pmorch/8xkeh/43
Peter V. Mørch
1
Usar $ compile e manipular DOM dentro de um controlador levará você a um caminho de código não testável. As diretivas devem ser usadas para alterar o DOM, nunca um controlador.
Enzey
57

Outra solução caso você não tenha controle sobre o conteúdo dinâmico

Isso funciona se você não carregou seu elemento por meio de uma diretiva (ou seja, como no exemplo dos jsfiddles comentados).

Resuma o seu conteúdo

Envolva seu conteúdo em um div para que você possa selecioná-lo se estiver usando JQuery . Você também pode optar por usar javascript nativo para obter seu elemento.

<div class="selector">
    <grid-filter columnname="LastNameFirstName" gridname="HomeGrid"></grid-filter>
</div>

Use Injetor Angular

Você pode usar o código a seguir para obter uma referência para $ compile se não tiver um.

$(".selector").each(function () {
    var content = $(this);
    angular.element(document).injector().invoke(function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    });
});

Resumo

A postagem original parecia assumir que você tinha uma referência de $ compile à mão. É obviamente fácil quando você tem a referência, mas eu não tinha, então essa foi a resposta para mim.

Uma advertência do código anterior

Se você estiver usando um pacote asp.net/mvc com cenário minify, terá problemas ao implantar no modo de lançamento. O problema vem na forma de Erro não detectado: [$ injector: despr], que é causado pelo minificador que bagunça o código javascript angular.

Aqui está a maneira de remediar isso :

Substitua o trecho de código anterior com a seguinte sobrecarga.

...
angular.element(document).injector().invoke(
[
    "$compile", function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    }
]);
...

Isso me causou muita dor antes de eu juntar as peças.

Jwize
fonte
1
Obrigado, pois o código acima funcionou para mim, o escopo e a explicação sobre a compilação estar disponível. às vezes você perde as partes fáceis :)
Abs
Tive de $ digerir após a compilação.
Knu
2
Absoluto salva-vidas, adicionei uma verificação condicional para angular em meu código, o que permite que ele use todas as
vantagens
18

Adição à resposta de @jwize

Porque angular.element(document).injector()estava dando erro injector is not defined Então, eu criei uma função que você pode executar após a chamada AJAX ou quando o DOM é alterado usando jQuery.

  function compileAngularElement( elSelector) {

        var elSelector = (typeof elSelector == 'string') ? elSelector : null ;  
            // The new element to be added
        if (elSelector != null ) {
            var $div = $( elSelector );

                // The parent of the new element
                var $target = $("[ng-app]");

              angular.element($target).injector().invoke(['$compile', function ($compile) {
                        var $scope = angular.element($target).scope();
                        $compile($div)($scope);
                        // Finally, refresh the watch expressions in the new element
                        $scope.$apply();
                    }]);
            }

        }

use-o passando apenas pelo seletor de novo elemento. como isso

compileAngularElement( '.user' ) ; 
shyammakwana.me
fonte
Obrigado, isso salvou minha vida com um pacote que usava UIKit para seus diálogos, com HTML gerado instantaneamente dentro de uma função $ scope. Como uma observação lateral, eu tive que alterar o seletor do $ target para atender às minhas necessidades, no meu caso $ ["data-ng-controller]"), e comentar $ scope.apply (); uma vez que esta função já foi chamada em um retorno de chamada angular (portanto, a aplicação já estava ocorrendo)
zontar
@zontar você também pode tornar targetdinâmico obtendo-o do argumento.
shyammakwana.me