Search code examples
knockout.jsknockout-postbox

Automatic disposal of ko postbox subscriptons


I have a single page app that consists of 2 main pieces:

1.-A top bar that has dynamic content(there is a shopping cart)
2.-A dynamic component that gets loaded based on the url.

A few times the components use postbox in order to communicate, the problem is that once the component itself is disposed the subscriptions created inside are not. I know I can manually add a dispose function to each component and then inside, dispose the subscriptions, but is there a way to do this in an automated way for all components?

I do know how to loop all properties and check if they are subscriptions, but I need a way to somehow attach this behavior to all components without manually attaching this dispose function to all of them.

I know postbox comes with a reset method I can call inside my routing library but I do not want to do that because then the top bar will lose its subscriptions too.

To give you some perspective, this is how the main index page looks like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Participant Dashboard</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- styles -->
    <link href="../css/bs3/bootstrap.css" rel="stylesheet">
    <link href="../css/bs3/override-bs3.css" rel="stylesheet">
    <script src="../scripts/global/requireConfig.js"></script>
    <script data-main="../consumer/scripts/require-config" src="../scripts/require.js"></script>

</head>
<body>
<top-bar params="routeParams:currentPageArguments"></top-bar>

<div data-bind="component: { name: currentPage, params: currentPageArguments }">
</div>


</body>
</html>

This is my custom component loader:

    function registerConventionLoader() {
        var koNamingConventionLoader = {
            getConfig: function (name, callback) {
                var widgetName;
                var widgetConfig = common.findComponentConfig(name);
                if (widgetConfig != null) {
                    widgetName = name.substr(widgetConfig.Prefix.length);
                    var widgetNamePascalCase = common.toPascalCase(widgetName);
                    var filePath = widgetConfig.Path;
                    var viewModelConfig = {require: filePath + widgetNamePascalCase};
                    var templateConfig = {require: "text!" + filePath + widgetNamePascalCase + '.html'};

                    callback({viewModel: viewModelConfig, template: templateConfig});
                }
                else {
                    
                    callback(null);
                }
            }
        };
        ko.components.loaders.push(koNamingConventionLoader);
    }


Solution

  • Found it! I had to get the viewmodel from require, then attach the dispose function and finally pass it in the callback: Thanks @user3297291 for guiding me in the correct direction

    var koNamingConventionLoader = {
                getConfig: function (name, callback) {
                   
                    var widgetName;
                    var widgetConfig = common.findComponentConfig(name);
                    
                    if (widgetConfig != null) {
                        widgetName = name.substr(widgetConfig.Prefix.length);
                        var widgetNamePascalCase = common.toPascalCase(widgetName);
                        var filePath = widgetConfig.Path;
                        
                        require([filePath + widgetNamePascalCase], function (mainViewModel) {
                            mainViewModel.prototype.dispose = function () {
                                var self = this;
                                for (var property in self) {
                                    if (Boolean(self[property]) && typeof self[property].dispose === "function") {
                                        
                                        self[property].dispose();
                                    }
                                }
                            };
                            
                            var templateConfig = {require: "text!" + filePath + widgetNamePascalCase + '.html'};
    
                            callback({viewModel: mainViewModel, template: templateConfig});
                        });
                    }
                    else {
                        
                        console.log("widget name not resolved", name);
                        callback(null);
                    }
                }
            };
            ko.components.loaders.push(koNamingConventionLoader);
        }