RequireJS

Knockout Component Loading with requirejs won’t load html file

Knockout 3.2 introduces component loading – this is a great feature that allows you to load HTML and JS modules into your code, thus enabling you to split your code in to self contained modules.

Think of it like PartialViews in MVC but for KnockoutJS.

The first thing you need to do is ‘register’ your component, e.g. (And this is taken from the KnockoutJS documentation)

ko.components.register('like-widget', {
    viewModel: function(params) {
        // Data: value is either null, 'like', or 'dislike'
        this.chosenValue = params.value;
        
        // Behaviors
        this.like = function() { this.chosenValue('like'); }.bind(this);
        this.dislike = function() { this.chosenValue('dislike'); }.bind(this);
    },
    template:
        '<div class="like-or-dislike" data-bind="visible: !chosenValue()">\
            <button data-bind="click: like">Like it</button>\
            <button data-bind="click: dislike">Dislike it</button>\
        </div>\
        <div class="result" data-bind="visible: chosenValue">\
            You <strong data-bind="text: chosenValue"></strong> it\
        </div>'
});

 

Then your main page would implement the following
    <ul data-bind="foreach: products">
        <li class="product">
            <strong data-bind="text: name"></strong>
            <like-widget params="value: userRating"></like-widget>
        </li>
    </ul>
with the following javascript to load your view model
    function Product(name, rating) {
        this.name = name;
        this.userRating = ko.observable(rating || null);
    }

    function MyViewModel() {
        this.products = [
            new Product('Garlic bread'),
            new Product('Pain au chocolat'),
            new Product('Seagull spaghetti', 'like') // This one was already 'liked'
        ];
    }

    ko.applyBindings(new MyViewModel());
All pretty cool – except we’re embedding our ‘module’ in the component registration.  What we need to do is have it all in a separate file.  Again the Knockout Documentation shows us how to simply do this – we use an AMD module loader such as RequireJA, thus we can store everything in separate files, thus:
component-like-widget.js 
define(['knockout'], function(ko) {

    function LikeWidgetViewModel(params) {
        this.chosenValue = params.value;
    }

    LikeWidgetViewModel.prototype.like = function() {
        this.chosenValue('like');
    };

    LikeWidgetViewModel.prototype.dislike = function() {
        this.chosenValue('dislike');
    };

    return LikeWidgetViewModel;

});
component-like-widget.html
<div class="like-or-dislike" data-bind="visible: !chosenValue()">
            <button data-bind="click: like">Like it</button>
            <button data-bind="click: dislike">Dislike it</button>
        </div>
        <div class="result" data-bind="visible: chosenValue">
            You <strong data-bind="text: chosenValue"></strong> it
And this was loaded from an external file
        </div>
So now our component registration needs to change to take account of the external files
ko.components.register('like-or-dislike', {
    viewModel: { require: 'files/component-like-widget' },
    template: { require: 'text!files/component-like-widget.html' }
});
 And then finally our main HTML page just implements the component similar to above (except we’re now adding products dynamically as well)

HTML

    <ul data-bind="foreach: products">
        <li class="product">
            <strong data-bind="text: name"></strong>
            <like-or-dislike params="value: userRating"></like-or-dislike>
        </li>
    </ul>
    <button data-bind="click: addProduct">Add a product</button>

script

    function Product(name, rating) {
        this.name = name;
        this.userRating = ko.observable(rating || null);
    }

    function MyViewModel() {
        this.products = ko.observableArray(); // Start empty
    }

    MyViewModel.prototype.addProduct = function() {
        var name = 'Product ' + (this.products().length + 1);
        this.products.push(new Product(name));
    };

    ko.applyBindings(new MyViewModel());

Now, this is pretty much was the Knockout documentation says – it works lovely on their own pages, but could I get it to work in my project?  Nope.  Not at all.

So, time to start digging.  First thing let’s have a look at what is being brought back from the server (I Use fiddler).

The javascript file loads fine – but when it ties to load the HTML file it does;t actually try to load it, instead it tries to get a file called ‘text’

Well I guess this makes sense – after all we’ve told it to load ‘text!widget-like-component.html’

So er, what am I missing?

Well I assumed requirejs automatically knew what when it saw !text it would know how to handle it.  Well you know what they same about ASS U ME

Now this is mainly because I’ve never really delved deep into requirejs – I just use it and out of the box it always works fine.  So, just in case anyone else out there is in the same boat here’s the fix.

To make requirejs know how to handle !text we need a !text plug in – an NuGet package apply named ‘Text Plugin for RequireJS’ – so install it with NuGet, and voila! The world is back to normal.