Как очистить / удалить наблюдаемые привязки в Knockout.js?

113

Я создаю функциональность на веб-странице, которую пользователь может выполнять несколько раз. В результате действия пользователя объект / модель создается и применяется к HTML с помощью ko.applyBindings ().

HTML-код с привязкой к данным создается с помощью шаблонов jQuery.

Все идет нормально.

Когда я повторяю этот шаг, создавая второй объект / модель и вызывая ko.applyBindings (), я сталкиваюсь с двумя проблемами:

  1. В разметке отображается предыдущий объект / модель, а также новый объект / модель.
  2. Возникает ошибка javascript, связанная с одним из свойств объекта / модели, хотя он все еще отображается в разметке.

Чтобы обойти эту проблему, после первого прохода я вызываю jQuery .empty (), чтобы удалить шаблонный HTML, содержащий все атрибуты привязки данных, чтобы его больше не было в DOM. Когда пользователь запускает процесс для второго прохода, HTML-код с привязкой к данным повторно добавляется в DOM.

Но, как я уже сказал, когда HTML повторно добавляется в DOM и повторно привязан к новому объекту / модели, он по-прежнему включает данные из первого объекта / модели, и я все равно получаю ошибку JS, которая не возникает. во время первого прохода.

Вывод, по-видимому, таков, что Knockout сохраняет эти связанные свойства, даже несмотря на то, что разметка удалена из DOM.

Я ищу средство удаления этих связанных свойств из Knockout; говорит нокаут, что наблюдаемой модели больше нет. Есть ли способ сделать это?

РЕДАКТИРОВАТЬ

Основной процесс заключается в том, что пользователь загружает файл; затем сервер отвечает объектом JSON, HTML с привязкой к данным добавляется в DOM, затем объектная модель JSON привязывается к этому HTML с помощью

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

После того, как пользователь сделал выбор в модели, тот же объект отправляется обратно на сервер, связанный с данными HTML удаляется из DOM, а затем у меня появляется следующий JS

mn.AccountCreationModel = null;

Когда пользователь желает сделать это еще раз, все эти шаги повторяются.

Боюсь, что код слишком "запутан" для демонстрации jsFiddle.

AWJ
источник
Не рекомендуется вызывать ko.applyBindings несколько раз, особенно для одного и того же содержащего элемент dom. Может быть другой способ добиться желаемого. Однако вам нужно будет предоставить больше кода. Если возможно, включите jsfiddle.
madcapnmckay
Почему бы не предоставить initфункцию, в которой вы передаете данные для применения?
KyorCode

Ответы:

169

Вы пробовали вызвать метод чистых узлов knockout для своего элемента DOM, чтобы избавиться от связанных с памятью объектов?

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

Затем повторное применение привязок нокаута только к этому элементу с новыми моделями представления обновит привязку представления.

KodeKreachor
источник
33
Это работает - спасибо. Однако я не могу найти никакой документации по этому методу.
awj
Я тоже искал документацию по этой служебной функции. Насколько я могу судить из исходного кода, он вызывает deleteопределенные ключи на самих элементах dom, которые, по-видимому, являются местом, где хранится вся магия нокаута. Если у кого-то есть источник документации, я был бы очень признателен.
Patrick M
2
Вы его не найдете. Я искал много и мало документов по служебным функциям ko, но их не было. Это сообщение в блоге - самое близкое из того, что вы найдете, но оно касается только участников ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Ник Дэниэлс
1
Вы также захотите вручную удалить события, как показано в моем ответе ниже.
Michael Berkompas
1
@KodeKreachor Я уже опубликовал рабочий пример ниже. Я полагал, что, возможно, лучше сохранить привязку нетронутой, вместо этого выпуская данные в ViewModel. Таким образом, вам никогда не придется иметь дело с отключением / повторной привязкой. Это кажется более чистым, чем использование недокументированных методов для ручной отмены привязки непосредственно из DOM. Кроме того, CleanNode проблематичен, потому что он не освобождает ни один из обработчиков событий (подробнее см. Ответ здесь: stackoverflow.com/questions/15063794/… )
Zac
31

Для проекта, над которым я работаю, я написал простую ko.unapplyBindingsфункцию, которая принимает узел jQuery и логическое значение удаления. Сначала он отвязывает все события jQuery, поскольку ko.cleanNodeметод не заботится об этом. Я проверил утечку памяти, и, похоже, все работает нормально.

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]);
    }
};
Майкл Беркомпас
источник
Только одно предостережение: я не тестировал повторное связывание с тем, что только что было ko.cleanNode()вызвано, а не с замененным HTML целиком.
Michael Berkompas
4
не отвяжет ли ваше решение все другие привязки событий? есть ли возможность удалить только обработчики событий ko?
lordvlad
без изменения ядра ко, которое есть
lordvlad
1
Верно, однако, насколько я могу судить, без изменения ядра это невозможно. См. Этот вопрос, который я поднял здесь: github.com/SteveSanderson/knockout/issues/724
Майкл Беркомпас
Разве идея нокаута не состоит в том, что вы должны очень редко самому прикасаться к дому? Этот ответ проходит через dom и, безусловно, будет далеко не бесполезным в моем случае использования.
Blowsie
12

Вы можете попробовать использовать привязку with, которую предлагает knockout: http://knockoutjs.com/documentation/with-binding.html Идея состоит в том, чтобы применить привязки один раз, и всякий раз, когда ваши данные изменяются, просто обновляйте свою модель.

Допустим, у вас есть модель представления storeViewModel верхнего уровня, ваша корзина представлена ​​cartViewModel и список элементов в этой корзине - скажем, cartItemsViewModel.

Вы должны привязать модель верхнего уровня - storeViewModel ко всей странице. Затем вы можете разделить части своей страницы, которые отвечают за корзину или элементы корзины.

Предположим, что cartItemsViewModel имеет следующую структуру:

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

В начале cartItemsViewModel может быть пустым.

Шаги будут выглядеть так:

  1. Определите привязки в html. Отделите привязку 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. Модель магазина поступает с вашего сервера (или создается другим способом).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Определите пустые модели в вашей модели представления верхнего уровня. Затем структура этой модели может быть обновлена ​​фактическими данными.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Свяжите модель представления верхнего уровня.

    ko.applyBindings(storeViewModel);

  5. Когда объект cartItemsViewModel доступен, назначьте его ранее определенному заполнителю.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Если вы хотите очистить корзину: storeViewModel.cartItemsViewModel(null);

Knockout позаботится о html - то есть он появится, когда модель не пуста, и содержимое div (с пометкой «с привязкой») исчезнет.

Сильвестр Гризио
источник
9

Мне нужно вызывать ko.applyBinding каждый раз, когда нажимается кнопка поиска, и отфильтрованные данные возвращаются с сервера, и в этом случае для меня следующая работа без использования ko.cleanNode.

Я испытал, если мы заменим foreach шаблоном, тогда он должен работать нормально в случае коллекций / observableArray.

Вы можете найти этот сценарий полезным.

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

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>
Аамир Саджад
источник
1
Я потратил как минимум 4 часа, пытаясь решить подобную проблему, и у меня работает только решение aamir.
Антонин Елинек
@AntoninJelinek - еще одна вещь, которую я испытал в своем сценарии, - полностью удалить html и динамически добавить его снова, чтобы полностью удалить все. например, у меня есть контейнер кода Knockout div <div id = "knockoutContainerDiv"> </div> в результате $ .Ajax Success, который я выполняю каждый раз, когда серверный метод вызывает $ ("# knockoutContainerDiv"). children.remove (); / / remove его содержимое вызвать метод для добавления динамического html с кодом вывода $ ("# knockoutContainerDiv"). append ("childelements with knockout binding code") и снова вызвать applyBinding
aamir sajjad
1
Привет, Аммир, у меня был почти такой же сценарий, как и у тебя. Все, что вы упомянули, отлично работало, кроме того факта, что мне пришлось использовать ko.cleanNode (element); перед каждой переплеткой.
Радослав Минчев
@RadoslavMinchev, как вы думаете, я могу вам помочь, если да, то как, я буду счастлив поделиться своими мыслями / опытом по конкретному вопросу.
aamir sajjad
Спасибо. @aamirsajjad, просто хотел упомянуть, что у меня сработало то, что я вызвал функцию cleanNode (), чтобы она работала.
Радослав Минчев
6

Вместо использования внутренних функций KO и работы с удалением обработчика событий JQuery, гораздо лучше использовать привязки withили template. Когда вы это делаете, ko воссоздает эту часть DOM, и поэтому она автоматически очищается. Это также рекомендуемый способ, см. Здесь: https://stackoverflow.com/a/15069509/207661 .

Шитал Шах
источник
4

Я думаю, что было бы лучше сохранить привязку все время и просто обновить связанные с ней данные. Я столкнулся с этой проблемой и обнаружил, что простой вызов .resetAll()метода для массива, в котором я хранил свои данные, был наиболее эффективным способом сделать это.

По сути, вы можете начать с некоторой глобальной переменной, которая содержит данные для визуализации через ViewModel:

var myLiveData = ko.observableArray();

Мне потребовалось время, чтобы понять, что я не могу просто создать myLiveDataобычный массив - ko.oberservableArrayдеталь была важна.

Тогда вы можете идти вперед и делать все, что хотите myLiveData. Например, $.getJSONпозвоните:

$.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);
});

Как только вы это сделаете, вы можете продолжить и применить привязки, используя ViewModel как обычно:

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

Затем в HTML используйте myDataкак обычно.

Таким образом, вы можете просто испортить myLiveData из любой функции. Например, если вы хотите обновлять каждые несколько секунд, просто заключите эту $.getJSONстроку в функцию и вызовите setIntervalее. Вам никогда не придется снимать привязку, если вы не забудете сохранить myLiveData.removeAll();линию.

Если ваши данные не действительно огромны, пользователь даже не сможет заметить время между сбросом массива и последующим добавлением самых последних данных обратно.

Зак
источник
За время, прошедшее с момента публикации вопроса, я сейчас этим занимаюсь. Просто некоторые из этих методов Knockout либо недокументированы, либо вам действительно нужно осмотреться (уже зная имя функции), чтобы найти, что она делает.
awj
Мне тоже пришлось очень постараться, чтобы найти это (когда количество часов копания в документации превышает итоговое количество строк кода ... вау). Рад, что у тебя все получилось.
Zac
2

Недавно у меня возникла проблема с утечкой памяти, и я ko.cleanNode(element);не хотел этого делать - ko.removeNode(element);сделал. Утечка памяти Javascript + Knockout.js - Как убедиться, что объект уничтожается?

Матас Вайткявичюс
источник
В knockout 3.1 ko.removeNode фактически вызывает ko.cleanNode. Однако я не знаю, что это было в случае с более ранними версиями.
кипрейней
1

Вы думали об этом:

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

Я придумал это, потому что в Knockout я нашел этот код

    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);
    }

Так что для меня это не проблема, которая уже связана, это то, что ошибка не была обнаружена и устранена ...

ozzy432836
источник
0

Я обнаружил, что если модель представления содержит много привязок div, лучший способ очистить их ko.applyBindings(new someModelView);- это использовать: ko.cleanNode($("body")[0]);это позволяет ko.applyBindings(new someModelView2);динамически вызывать новый, не беспокоясь о том, что предыдущая модель представления все еще привязана.

Деррик Хэмптон
источник
4
Я хотел бы добавить пару моментов: (1) это очистит ВСЕ привязки с вашей веб-страницы, что может подойти вашему приложению, но я полагаю, что существует множество приложений, в которых привязки были добавлены к нескольким частям страницы для отдельных причины. Для многих пользователей может оказаться бесполезным очистить ВСЕ привязки одной быстрой командой. (2) более быстрый, более эффективный, нативный метод JavaScript извлечения $("body")[0]является document.body.
awj 04