2016-03-21 10 views
4

Also hier ist ein seltsames KnockoutJS Problem, das ich noch nie zuvor gesehen habe.Knockout JS löscht keine Komponenten

Ich arbeite an einer Anwendung, die Knockout-Komponenten sehr stark verwendet.

In einem Teil der App habe ich eine Editor-Seite, die dynamisch von einem JSON-basierten Backend erstellt wird und die eine Frontend-Seite mit einer Anzahl von Widgets belegt, abhängig davon, was aus den Back-End-Daten erzählt wird.

Beispiel Das hintere Ende könnte senden

[{"widget": "textBox"},{"widget": "textBox"},{"widget": "comboBox"},{"widget": "checkBox"}] 

die das vordere Ende führen würde eine Seite mit

<html> 
    .... 
    <textbox></textbox> 
    <textbox></textbox> 
    <combobox></combobox> 
    <checkbox></checkbox> 
    .... 
</html> 

Jede der benutzerdefinierten Tags aufzubauen ist eine individuelle KnockoutJS Komponente, zusammengestellt als ein AMD-Modul und geladen mit RequireJS, jede Komponente basiert auf der gleichen Boiler-Platte:

Die Komponenten kommunizieren miteinander und mit der Seite unter Verwendung von "Knockout Postbox" in einer Pub Sub Mode.

Und wenn ich sie in die Seite, die ich so in den folgenden Herren tun:

<div data-bind="foreach: pageComponentsToDisplay"> 
    <!-- ko if: widget == "textBox" --> 
    <textBox params="details: $data"></textBox> 
    <!-- /ko --> 

    <!-- ko if: widget == "comboBox" --> 
    <comboBox params="details: $data"></comboBox> 
    <!-- /ko --> 

    <!-- ko if: widget == "checkBox" --> 
    <checkBox params="details: $data"></checkBox> 
    <!-- /ko --> 
</div> 

und wo pageComponentsToDisplay einen einfachen KO-beobachtbaren Array, das schiebe ich nur die Objekte aus dem Backend erhielten auf:

pageComponentsToDisplay = ko.observableArray([]); 
pageComponentsToDisplay(data); 

Wo ‚Daten‘ wie oben

nun all dies funktioniert gut, aber hier in liegt nun das ODD in JSON gezeigt.

Wenn ich ein „reload“ der Seite zu tun, habe ich einfach

pageComponentsToDisplay = ko.observableArray([]); 

das Array zu löschen und damit alle meine Komponenten auch von der Seite verschwinden, wie erwartet, aber wenn ich lade die neuen Daten in wieder mit:

pageComponentsToDisplay(data); 

ich meine neue Komponenten auf dem Bildschirm erhalten, wie erwartet, aber die alten erscheinen nach wie vor vorhanden und aktiv im Speicher zu sein, auch wenn es nicht sichtbar.

Der Grund, warum ich die Steuerelemente kenne, ist immer noch da, denn wenn ich eine meiner PubSub-Nachrichten ausstelle, um die Steuerelemente nach Statusinformationen zu fragen, antworten ALLE.

Es scheint mir, dass wenn ich das Array lösche, und wenn KO das Ansichtsmodell löscht, scheint es nicht wirklich die alten Kopien zu zerstören.

Weiter, wenn ich wieder auffrischen, bekomme ich dann 3 Sätze von Komponenten reagieren, aktualisieren Sie erneut und es ist 4, und dies erhöht sich wie erwartet.

Dies ist das erste Mal, dass ich dieses Verhalten mit Knockout habe, und ich habe diese Art von Muster seit Jahren ohne ein Problem verwendet.

Wenn Sie einen guten Überblick über wollen, wie das gesamte Projekt eingerichtet ist, habe ich eine Probe Skelett Layout auf meiner GitHub Seite:

https://github.com/shawty/dotnetnotts15

Wenn jemand irgendwelche Ideen auf, hat das, was hier passiert sein könnte Ich würde sie gerne hören.

Als letzte Anmerkung entwickle ich eigentlich alles mit Typescript, aber da dies ein Laufzeitproblem ist, dokumentiere ich es aus Sicht von JS.

Grüße Shawty

Update 1

So nach Graben weiter (und mit einem wenig ‚neuen Denken‘ dank cl3m Antwort) Ich bin ein wenig weiter nach vorne.

In meinem ersten Post habe ich erwähnt, dass ich Ryan Niemeyers excellente PubSub Erweiterung für Knockout 'ko Postbox' verwendet.

Es stellt sich heraus, dass meine 'Komponenten' entsorgt und abgerissen werden, ABER die Subskriptions-Handler, die erstellt werden, um auf Postfach zu reagieren, sind nicht.

Das Ergebnis ist, dass die VM (oder genauer gesagt die Werte, die das Abonnement in der VM verwendet) zusammen mit dem Postbox-Abonnement-Handler im Speicher gehalten werden.

Das bedeutet, wenn mein Master eine Nachricht sendet, die nach Komponentenwerten fragt, antwortet die gehaltene Referenz, gefolgt von der sichtbar aktiven Komponente.

Was ich jetzt tun muss, ist eine Möglichkeit, diese Subskriptionen zu disponieren, die, weil ich Postbox direkt benutze und sie keinem Observablen in meinem Modell zuordne, bedeutet, dass ich nicht wirklich ein var oder habe Objektreferenz, um sie anzusprechen.

Die Suche geht weiter.

Update 2

unten auf die Frage meiner Selbst Antwort sehen.

+0

Verwenden Sie die Methode 'ko.utils.domNodeDisposal.addDisposeCallback()', um Ihre Knockout-Komponenten zu entfernen, nachdem die entsprechenden Elemente aus dem 'DOM' entfernt wurden? – cl3m

+0

Nicht, dass ich mir bewusst, ich lösche nur das Array und ich nahm an, dass die Instanzen meiner Komponenten automatisch riss. Ich muss jedoch gestehen, als ich dies am Freitag recherchiert habe, bin ich auf die Dokumente gestoßen, die es erwähnt haben, aber ich habe es noch nicht benutzt. Kurz gesagt, wenn KO es nicht automatisch für mich macht, dann NEIN, ich bin es nicht. – shawty

+0

Ich verwende diese Methode für benutzerdefinierte Bindungen, ich dit es nicht auf Komponenten aber ... – cl3m

Antwort

1

Das Problem war es auf Grund scheint hängen von Postbox aufgebaut auf Abonnements Knockout, wenn die tatsächlichen Komponenten aktiv, wo.

In meinem Fall verwende ich Postbox lediglich eine Messaging-Plattform, so dass alle i

tue
ko.postbox.subscribe("foo", function(payload) { ... }); 

die ganze Zeit war, da ich immer nur auf diese Weise einzigen Schuss Abonnements verwenden, war ich Nie zahlen ANY Aufmerksamkeit auf die Werte, die durch den Postbox-Subskriptionsaufruf zurückgegeben werden.

Ich habe die Dinge auf diese Weise getan, einfach weil in vielen der Komponenten, die ich erstelle, eine gemeinsame API ist, die sie alle verwenden, auf die sie alle unterschiedlich reagieren, also alles, was ich je brauchte, war einfach zu tun, wenn Ihr aufgerufener Handler komponentenspezifisch, aber nicht anwendungsspezifisch war.

Es stellt sich jedoch heraus, dass wenn Sie postbox auf diese Weise verwenden, es keine beobachtbare für Sie gibt, und als solche gibt es nichts zu beseitigen. (Sie nicht die Rückkehr zu speichern, so dass Sie nichts mit zu arbeiten) Dokumentation nicht erwähnt

Was der Knockouts und Postbox, ist, dass der Rückgabewert von postbox.subscribe eine allgemeinen Knockout-Abonnement-Funktion ist, und durch die Zuordnung die Rückkehr von dieser zu einer Eigenschaft in Ihrem Modell, haben Sie dann ein Mittel, um die Funktionalität auf sie zugreifen, eine dieser Funktionen bietet die Möglichkeit "dispose" die Instanz, die NICHT nur die physische Manifestation der Komponente entfernt aus seiner Sammlung, ABER stellt auch sicher, dass alle Abonnements oder Event-Handler, die damit verbunden sind, auch korrekt abgerissen werden.

Paar mit, dass die Tatsache, dass Sie eine dispose Handler Ihre VM passieren kann, wenn Sie es registrieren, ist die endgültige Lösung sicherstellen, dass Sie die folgenden

/// <amd-dependency path="text!application/components/pagecontrols/template.html" /> 
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) { 
    var Template = require("text!application/components/pagecontrols/template.html"); 
    var ViewModel = (function() { 
     function ViewModel(params) { 
      var _this = this; 
      this.someDataBoundVar = ko.observable(""); 
      this.mySubscriptionHandler = ko.postbox.subscribe("foo", function(){ 
       // do something here to handle subscription message 
      }); 
     } 
     ViewModel.prototype.somePublicFunction = function() { 
      postbox.publish("SomeMessage", { data: "some data" }); 
     }; 
     return ViewModel; 
     ViewModel.prototype.dispose = function() { 
      this.mySubscriptionHandler.dispose(); 
     }; 
     return ViewModel; 
    })(); 
    return { viewModel: ViewModel, template: Template, dispose: this.dispose }; 
}); 

Sie werden Sie feststellen, dass die resultierende Klasse hat auch eine "dispose" -Funktion, dies ist etwas, das KnockoutJS für Komponentenklassen bereitstellt, und wenn Ihre Klasse als eine Komponente von der Haupt-KO-Bibliothek verwaltet wird, sucht KO und findet diese Funktion, wenn Ihre Komponentenklasse geht außer Reichweite.

Wie Sie in meinem Beispiel sehen können, hat Iv'e die Rückgabe von der Subscription-Handler wie bereits erwähnt gespeichert, dann in diesem Hook-Punkt, den wir wissen, wird aufgerufen, um sicherzustellen, dass ich auch bei jedem Abonnement dispise aufrufen .

Natürlich zeigt dies NUR ein Abonnement, wenn Sie mehrere Abonnements haben, dann benötigen Sie mehrere speichert und mehrere Anrufe am Ende. Eine einfache Möglichkeit, dies zu erreichen, insbesondere wenn Sie Typoskript wie ich verwenden, besteht darin, Generic-Funktionalität von Typescripts zu verwenden und alle Ihre Subskriptionen in ein typisiertes Array zu speichern, dh am Ende müssen Sie nur das Array durchlaufen und dispose aufrufen jeder Eintrag darin.

+0

Du hast mir wahrscheinlich ein sehr frustrierendes paar Tage gerettet, um den Grund dafür herauszufinden! Vielen Dank für diese Antwort! –

+0

Ihr mehr als willkommen Jamie. Dieses Ding ließ mich mindestens eine Woche im Kreis herumlaufen. Als zusätzlichen Bonus, wenn Sie Aurelia verwenden, ist der Prozess genau der gleiche mit der Eventing-Bibliothek in diesem. – shawty

+0

Fantastisch. Ich benutze Aurelia nicht, kann sie aber für zukünftige Projekte verwenden, also danke für den Tipp. Alles Gute! Jamie –

1

Ich bin mir nicht sicher, dass dies helfen wird, aber wie ich meine Kommentar, hier ist, wie ich die ko.utils.domNodeDisposal.addDisposeCallback() in meinem custom bindings. Vielleicht gibt es eine Möglichkeit, es in Knockout zu verwenden components:

ko.bindingHandlers.tooltip = { 
    init: function(element, valueAccessor) { 
     $(element).tooltip(options); 
     ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
     $(element).tooltip('destroy'); 
     }); 
    } 
} 

Mehr Lesung am Ryan Niemeyer's website

+0

Danke wird das jetzt testen und verfolgen und es euch wissen lassen. – shawty

+0

Ich hatte nur einen Haken damit, nicht viel Hilfe, aber hat mir ein paar Ideen gegeben.Ich gehe davon aus, dass Sie damit Bootstrap-Komponenten ausschalten. – shawty

+0

Ja, unter anderem Bibliotheken, die ich auf meine Elemente anwende. Googeln für Ihr Problem, ich stolpere gerade über dieses Dokument: http://knockoutjs.com/documentation/component-binding.html#disposal-and-memory-management Vielleicht kann die 'dispose()' Methode helfen – cl3m