Search code examples
javascriptdurandaldurandal-2.0

What is the difference between returning a function and returning an object in a Durandal viewmodel?


I'm looking at implementing a wizard type system in my application and looking at the first wizard example on the dfiddle-2.0 project on GitHub. The step viewmodels are all functions though and I'm trying to understand why.

Here is what the dfiddle is using for the index.js of the wizard:

define(['durandal/activator', './step1', './step2', './step3', 'knockout'], function( activator, Step1, Step2, Step3, ko ) {

    var steps = [new Step1(), new Step2(), new Step3()];
    var step = ko.observable(0);
    var activeStep = activator.create();
    var stepsLength = steps.length;

    var hasPrevious = ko.computed(function() {
        return step() > 0;
    });

    var hasNext = ko.computed(function() {
        return (step() < stepsLength - 1);
    });

    // Start with first step
    activeStep(steps[step()]);

    return {
        showCodeUrl: true,
        steps: steps,
        step: step,
        activeStep: activeStep,
        next: next,
        previous: previous,
        hasPrevious: hasPrevious,
        hasNext: hasNext
    };

    function next () {
        if ( step() < stepsLength ) {
            step(step() + 1);
            activeStep(steps[step()]);
        }
    }

    function previous () {
        if ( step() > 0 ) {
            step(step() - 1);
            activeStep(steps[step()]);
        }
    }

});

And here is what it's using for step1.js

define(function() {

    return function() {
        this.name = 'Step 1';
        this.s1one = 'Unique to' + this.name;
        this.s1two = 'Another property unique to' + this.name;
    };

});

Here is what I'm currently using for index.js.

define(['knockout'],
    function (ko) {
        var rootPath = "viewmodels/wizards/steps/";
        var steps = ["step1", "step2", "step3"];
        var step = ko.observable(0);
        var activeStep = ko.observable(); 
        var stepLength = steps.length;

        var hasPrevious = ko.computed(function () { return step() > 0 });
        var hasNext = ko.computed(function () { return step() < stepLength - 1 });

        var activate = function () {
            return activeStep(rootPath + steps[step()]);
        };

        return {
            steps: steps,
            step: step,
            activeStep: activeStep,
            next: next,
            previous: previous,
            hasPrevious: hasPrevious,
            hasNext: hasNext,
            activate: activate
        }

        function next() {
            if (hasNext()) {
                step(step() + 1);
                activeStep(rootPath + steps[step()]);
            }
        }

        function previous() {
            if (hasPrevious()) {
                step(step() - 1);
                activeStep(rootPath + steps[step()]);
            }
        }
    });

And my step1.js

define(function () {
    var name = ko.observable("Step 1");
    var s1one = ko.observable("Unique to " + name());
    var s1two = ko.observable("Another property unique to " + name());
    var returnVm = {
        name: name,
        s1one: s1one,
        s1two: s1two
    };

    return returnVm;
});

The bindings are the same so how are these two approaches different? What am I losing by just returning an object instead of using functions?


Solution

  • The difference is subtle, but important. Modules that return an object are singletons. The same object will be shared among all other modules that depend on it. Modules that return a function are termed constructor functions. Dependant modules will instantiate this constructor function with the new keyword. Therefore, each instance is unique.

    Here's some more information gleaned from the Durandal documentation:

    A module's define is only exeucted once, at the time the module is first required. As a result, if you return an object instance, you have created a singleton which will stay in memory for the lifetime of your application. If this is not desired, return a constructor function to retain greater control of the lifetime of your objects by allowing consumers to create/release them as needed.

    In your example, you aren't losing anything. Either approach works. Which is more correct depends on a number of things. If you do not require unique instances of your module each time it is required, then a singleton is the best choice. However, if say you need multiple instances of the same dialog module, but each with their own data, a constructor function is the way to go.

    I hope this helps.