Como limpar / remover ligações observáveis ​​em Knockout.js?

113

Estou criando uma funcionalidade em uma página da web que o usuário pode executar várias vezes. Por meio da ação do usuário, um objeto / modelo é criado e aplicado ao HTML usando ko.applyBindings ().

O HTML vinculado a dados é criado por meio de modelos jQuery.

Por enquanto, tudo bem.

Quando repito esta etapa criando um segundo objeto / modelo e chamo ko.applyBindings (), encontro dois problemas:

  1. A marcação mostra o objeto / modelo anterior, bem como o novo objeto / modelo.
  2. Um erro de javascript ocorre relacionado a uma das propriedades no objeto / modelo, embora ainda seja renderizado na marcação.

Para contornar esse problema, após a primeira passagem, chamo .empty () do jQuery para remover o HTML modelado que contém todos os atributos de vinculação de dados, de modo que não esteja mais no DOM. Quando o usuário inicia o processo para a segunda passagem, o HTML vinculado a dados é adicionado novamente ao DOM.

Mas, como eu disse, quando o HTML é adicionado novamente ao DOM e vinculado ao novo objeto / modelo, ele ainda inclui dados do primeiro objeto / modelo e ainda recebo o erro JS que não ocorre durante a primeira passagem.

A conclusão parece ser que Knockout está mantendo essas propriedades vinculadas, mesmo que a marcação seja removida do DOM.

Então, o que estou procurando é um meio de remover essas propriedades associadas do Knockout; dizendo ao nocaute que não há mais um modelo observável. Existe uma maneira de fazer isso?

EDITAR

O processo básico é que o usuário carregue um arquivo; o servidor responde com um objeto JSON, o HTML vinculado a dados é adicionado ao DOM e o modelo de objeto JSON é vinculado a este HTML usando

mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);

Depois que o usuário fez algumas seleções no modelo, o mesmo objeto é postado de volta no servidor, o HTML vinculado a dados é removido do DOM e eu tenho o seguinte JS

mn.AccountCreationModel = null;

Quando o usuário deseja fazer isso mais uma vez, todas essas etapas são repetidas.

Receio que o código esteja muito "envolvido" para fazer uma demonstração do jsFiddle.

awj
fonte
Chamar ko.applyBindings várias vezes, especialmente no mesmo elemento dom que contém, não é recomendado. Pode haver outra maneira de conseguir o que você deseja. Porém, você precisará fornecer mais código. Inclua um jsfiddle, se possível.
madcapnmckay
Por que não expõe uma initfunção na qual você passa os dados a serem aplicados?
KyorCode

Respostas:

169

Você já tentou chamar o método de nó limpo do knockout em seu elemento DOM para descartar os objetos vinculados à memória?

var element = $('#elementId')[0]; 
ko.cleanNode(element);

Em seguida, aplicar as ligações knockout novamente apenas naquele elemento com seus novos modelos de visualização atualizaria sua ligação de visualização.

KodeKreachor
fonte
33
Isso funciona - obrigado. No entanto, não consigo encontrar nenhuma documentação sobre esse método.
awj
Eu também pesquisei por documentação sobre essa função de utilitário nocauteador. Pelo que eu posso dizer pelo código-fonte, ele está chamando deletecertas chaves nos próprios elementos dom, que aparentemente é onde toda a magia nocauteadora está armazenada. Se alguém tiver uma fonte de documentação, ficaria muito grato.
Patrick M
2
Você não vai encontrar. Pesquisei em todos os lugares por documentos sobre funções de utilitário ko, mas não existe nenhum. Esta postagem do blog é a coisa mais próxima que você encontrará, mas cobre apenas os membros do ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels
1
Você também deseja remover manualmente os eventos, conforme mostrado na minha resposta abaixo.
Michael Berkompas
1
@KodeKreachor Eu já havia postado o exemplo de trabalho abaixo. Meu ponto é que pode ser melhor manter a vinculação intacta, enquanto libera os dados dentro do ViewModel. Dessa forma, você nunca terá que lidar com a desconexão / religação. Parece mais limpo do que usar métodos não documentados para desvincular manualmente diretamente do DOM. Além disso, CleanNode é problemático porque não libera nenhum dos manipuladores de eventos (veja a resposta aqui para mais detalhes: stackoverflow.com/questions/15063794/… )
Zac
31

Para um projeto no qual estou trabalhando, escrevi uma ko.unapplyBindingsfunção simples que aceita um nó jQuery e o booleano remove. Primeiro, ele desassocia todos os eventos jQuery, já que o ko.cleanNodemétodo não cuida disso. Testei vazamentos de memória e parece funcionar bem.

ko.unapplyBindings = function ($node, remove) {
    // unbind events
    $node.find("*").each(function () {
        $(this).unbind();
    });

    // Remove KO subscriptions and references
    if (remove) {
        ko.removeNode($node[0]);
    } else {
        ko.cleanNode($node[0]);
    }
};
Michael Berkompas
fonte
Só uma advertência: não testei a religação de algo que acabou de ser ko.cleanNode()chamado e nem todo o html foi substituído.
Michael Berkompas
4
sua solução não desvincularia todas as outras associações de evento também? existe a possibilidade de remover apenas os manipuladores de eventos do ko?
lordvlad
sem alterar o núcleo do ko que é
lordvlad
1
Verdade, no entanto, isso não é possível, pelo que posso dizer, sem uma alteração no núcleo. Veja este problema que mencionei
Michael Berkompas
Não é a ideia de nocaute que você muito raramente deve tocar o dom sozinho? Esta resposta percorre o dom e certamente estaria longe de ser inutilizável em meu caso de uso.
Blowsie
12

Você pode tentar usar o com vinculação que o knockout oferece: http://knockoutjs.com/documentation/with-binding.html A ideia é usar aplicar vinculações uma vez e, sempre que seus dados forem alterados, apenas atualize seu modelo.

Digamos que você tenha um modelo de visualização de nível superior storeViewModel, seu carrinho representado por cartViewModel e uma lista de itens nesse carrinho - digamos, cartItemsViewModel.

Você vincularia o modelo de nível superior - storeViewModel a toda a página. Em seguida, você pode separar as partes de sua página que são responsáveis ​​pelo carrinho ou itens do carrinho.

Vamos supor que cartItemsViewModel tenha a seguinte estrutura:

var actualCartItemsModel = { CartItems: [
  { ItemName: "FirstItem", Price: 12 }, 
  { ItemName: "SecondItem", Price: 10 }
] }

O cartItemsViewModel pode estar vazio no início.

As etapas seriam assim:

  1. Defina ligações em html. Separe a ligação cartItemsViewModel.

      
        <div data-bind="with: cartItemsViewModel">
          <div data-bind="foreach: CartItems">
            <span data-bind="text: ItemName"></span>
            <span data-bind="text: Price"></span>
          </div>
        </div>
      
    
  2. O modelo de loja vem do seu servidor (ou é criado de qualquer outra forma).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Defina modelos vazios em seu modelo de vista de nível superior. Em seguida, uma estrutura desse modelo pode ser atualizada com os dados reais.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Vincule o modelo de vista de nível superior.

    ko.applyBindings(storeViewModel);

  5. Quando o objeto cartItemsViewModel estiver disponível, atribua-o ao espaço reservado previamente definido.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Se você gostaria de limpar os itens do carrinho: storeViewModel.cartItemsViewModel(null);

Knockout cuidará do html - ou seja, aparecerá quando o modelo não estiver vazio e o conteúdo de div (aquele com "com ligação") desaparecerá.

Sylwester Gryzio
fonte
9

Tenho que chamar ko.applyBinding cada vez que clica no botão de pesquisa e os dados filtrados são retornados do servidor e, neste caso, o trabalho seguinte para mim sem usar ko.cleanNode.

Eu experimentei, se substituirmos foreach por template, ele deve funcionar bem no caso de coleções / observableArray.

Você pode achar este cenário útil.

<ul data-bind="template: { name: 'template', foreach: Events }"></ul>

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>
aamir sajjad
fonte
1
Passei pelo menos 4 horas tentando resolver um problema semelhante e apenas a solução de aamir funciona para mim.
Antonin Jelinek,
@AntoninJelinek a outra coisa que experimentei em meu cenário, remover completamente o html e anexá-lo novamente de forma dinâmica para remover totalmente tudo. por exemplo, eu tenho o contêiner do código Knockout div <div id = "knockoutContainerDiv"> </div> em $ .Ajax Resultado de sucesso que faço seguindo cada vez que o método do servidor chama $ ("# knockoutContainerDiv"). children.remove (); / / remove content of it chame o método para anexar html dinâmico com código de knockout $ ("# knockoutContainerDiv"). append ("childelements com código de ligação de knockout") e chamando applyBinding novamente
aamir sajjad
1
Ei, Ammir, tive praticamente o mesmo cenário que você. Tudo que você mencionou funcionou muito bem, exceto o fato de que tive que usar ko.cleanNode (element); antes de cada nova ligação.
Radoslav Minchev
@RadoslavMinchev você acha que posso ajudá-lo ainda mais, se sim, então como, ficarei feliz em compartilhar meus pensamentos / experiência sobre determinada questão.
aamir sajjad
THX. @aamirsajjad, só queria mencionar que o que funcionou para mim foi chamar a função cleanNode () para fazê-la funcionar.
Radoslav Minchev
6

Em vez de usar as funções internas do KO e lidar com a remoção do manipulador de eventos do JQuery, uma ideia muito melhor é usar withou templateligações. Quando você faz isso, ko recria essa parte do DOM e, portanto, ela é automaticamente limpa. Essa também é a forma recomendada, veja aqui: https://stackoverflow.com/a/15069509/207661 .

Shital Shah
fonte
4

Acho que seria melhor manter a vinculação o tempo todo e simplesmente atualizar os dados associados a ela. Eu encontrei esse problema e descobri que apenas chamar usando o .resetAll()método no array no qual eu estava mantendo meus dados era a maneira mais eficaz de fazer isso.

Basicamente, você pode começar com alguma var global que contém dados a serem renderizados por meio do ViewModel:

var myLiveData = ko.observableArray();

Levei um tempo para perceber que não podia simplesmente fazer myLiveDataum array normal - a ko.oberservableArrayparte era importante.

Então você pode ir em frente e fazer o que quiser myLiveData. Por exemplo, faça uma $.getJSONchamada:

$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
    myLiveData.removeAll();
    /* parse the JSON data however you want, get it into myLiveData, as below */
    myLiveData.push(data[0].foo);
    myLiveData.push(data[4].bar);
});

Depois de fazer isso, você pode prosseguir e aplicar vinculações usando seu ViewModel como de costume:

function MyViewModel() {
    var self = this;
    self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());

Então, no HTML, use myDatacomo faria normalmente.

Dessa forma, você pode simplesmente muck com myLiveData de qualquer função. Por exemplo, se você deseja atualizar a cada poucos segundos, basta envolver essa $.getJSONlinha em uma função e chamá setInterval-la. Você nunca precisará remover a ligação, desde que se lembre de manter a myLiveData.removeAll();linha dentro.

A menos que seus dados sejam realmente enormes, o usuário não será capaz de perceber o tempo entre a reconfiguração do array e a adição dos dados mais atuais de volta.

Zac
fonte
Desde que postei a pergunta, é o que faço agora. É só que alguns desses métodos Knockout não são documentados ou você realmente precisa dar uma olhada (já sabendo o nome da função) para descobrir o que eles fazem.
awj
Tive de procurar muito para encontrar isso também (quando o número de horas vasculhando documentos excede o número resultante de linhas de código ... uau). Feliz por você fazer isso funcionar.
Zac
1

Você já pensou sobre isso:

try {
    ko.applyBindings(PersonListViewModel);
}
catch (err) {
    console.log(err.message);
}

Eu vim com isso porque no Knockout, encontrei este código

    var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
    if (!sourceBindings) {
        if (alreadyBound) {
            throw Error("You cannot apply bindings multiple times to the same element.");
        }
        ko.utils.domData.set(node, boundElementDomDataKey, true);
    }

Então, para mim, não é realmente um problema que já esteja vinculado, é que o erro não foi detectado e corrigido ...

ozzy432836
fonte
0

Eu descobri que se o modelo de visão contém muitas ligações div, a melhor maneira de limpar o ko.applyBindings(new someModelView);é usar: ko.cleanNode($("body")[0]);Isso permite que você chame um novo ko.applyBindings(new someModelView2);dinamicamente sem a preocupação de o modelo de visão anterior ainda estar vinculado.

Derrick Hampton
fonte
4
Há alguns pontos que eu gostaria de adicionar: (1) isso limpará TODAS as ligações da sua página da web, o que pode ser adequado ao seu aplicativo, mas imagino que haja muitos aplicativos em que as ligações foram adicionadas a várias partes da página para separar razões. Pode não ser útil para muitos usuários limpar TODAS as ligações em um único comando de varredura. (2) um método de recuperação nativo de JavaScript mais rápido e eficiente $("body")[0]é document.body.
awj