8

I have a hierarchy of nested KnockoutJS Components using 3.2.0. It's working very well but I'm looking to execute some code once my entire hierarchy of components has been loaded and rendered. It's a rough equivalent of afterRender(), needed for the same common uses cases as afterRender.

I've tried a few approaches but no luck so far:

  1. Added the following to the root template but it gets called before the nested components are loaded, so too early. <!--ko template: {afterRender: onLoad.bind($data)} -->
  2. Using the latest 3.3.0-alpha and specifying synchronous:true on all components. But I believe since I'm using AMD, the components are still 'loaded' asynchronously which mean that just because my root applyBindings() returns, doesn't mean that all components have been loaded and rendered.
  3. Even tried building a collection of deferred objects that get resolved only when their corresponding components are loaded. This got overly complicated and still didn't work for reasons I won't go into.

Is there a way to get a callback called once a complete hierarchy of knockoutjs components have been loaded and rendered? Thanks!

I just came across these two threads so it seems others are looking for this as well. The key differentiator from the existing workarounds are they don't work with nested components.

Origineil
  • 3,108
  • 2
  • 12
  • 17
Rich Taylor
  • 106
  • 1
  • 5
  • Just curious, what does the console.log look like if you echo a "before xx" and "after xx" for each component? Perhaps its possible that the nested component fires a before/etc event properly, despite "after" not working right? That could be used to manually track when the process is finished. – Andrew Nov 27 '14 at 04:08
  • did you tried this ... data: dataItems will replace the foreach **data-bind="template: { name: 'template-name', data: dataItems, afterRender: callbackmethod}"** – Haridarshan Nov 27 '14 at 10:05

3 Answers3

3

I've written a knockout library that triggers an event when all components have been loaded and bound. It uses reference counting, similar to referencing counting used for garbage collection. I extensively use components in my project(s), including nesting many levels deep, and I can't live without knowing when everything is "ready to go". I haven't spend much time on documentation of usage, but the basics are there.

Git Hub wiki: https://github.com/ericraider33/ko.component.loader/wiki

Fiddle: https://jsfiddle.net/ericeschenbach/487hp5zf/embedded/result/

Usage HTML:

<div id="ko-div">
  Status: <span data-bind="text: loading() ? 'Loading' : 'Done'"></span>
  <br><br>
  <test-panel></test-panel>
</div>

Usage JS:

var pageModel = { 
  loading: ko.observable(true), 
    completedCallback: function (childRef) { 
    pageModel.loading(false); 
    childRef.testValue(childRef.testValue()+1);  
  }
};

var tpRef = ko.componentLoader.ref.child({ completedCallback: pageModel.completedCallback});
var tpModel = { 
  attached: function(element) { return tpRef; },
  testValue: ko.observable(5)
};

ko.components.register('test-panel', {
    viewModel: function() { return tpModel; },
    template: '<div data-bind="attached: true">Test Panel<br>From Code <span data-bind="text: testValue"></span></div>'
});


ko.componentLoader.setOptions({ verbose: true });
ko.applyBindings(pageModel, $('#ko-div')[0]);
raider33
  • 1,633
  • 1
  • 19
  • 21
  • Thanks for a nice plugin. One question though, why does it say `Component not found. Ignoring attached event for component object` in your fiddle? – Johan Mar 08 '16 at 12:24
  • I've updated the plugin to skip that warning. For my project, I use RequireJS and the plugin has some convenience methods for setting up require. The warning you saw is only relevant in that setup. Thanks for pointing that out. – raider33 Mar 08 '16 at 14:13
  • I see. I was a bit concerned that I was misusing it when I saw the message :) Thanks for elaborating – Johan Mar 08 '16 at 14:43
2

Here is what worked for me. I did not try it in all possible variations such as mixing sync and async components, or using custom component loaders.

There is a method in KO 3.3.0 that all components loading goes through:

ko.components = { get: function(componentName, callback) { ...

the get method is invoked with a desired componentName and when component has been loaded - a callback is invoked.

So all you need to do is wrap ko.components.get and callback and increment pendingComponentsCount on each call, and decrement it after callback is executed. When count reaches zero it means that all components were loaded.

25 lines of JS code (using underscorejs).

You also need to handle a special case where ko.applyBindings did not encounter any components, in which it also means that all components (all zero of them) were loaded.

Again, not sure if this works in every situation, but it seems to be working in my case. I can think of few scenarios where this can easily break (for example if somebody would cache a reference to ko.components.get before you get to wrap it).

THX-1138
  • 21,316
  • 26
  • 96
  • 160
1

If you'r working with ko.components this might be of use:

1) Create a deferred object to keep track of each component loading

var statusX = $.Deferred()
var statusY = $.Deferred()

2) Inform knockout to tell you when the component is loaded and ready

ko.components.get('x-component', statusX.resolve) //Note: not calling resolve, but passing the function
ko.components.get('y-component', statusY.resolve)

3) Synch up both status deferreds

$.when(statusX.promise(), statusY.promise())
 .done( function allComponentsLoaded(componentX, componentY){ 
            //Both components are ready here 
            //Note the arguments from the function comes via
            //ko->jquery deferred resolve
           });
Frison Alexander
  • 3,228
  • 2
  • 29
  • 32
  • a) it is a workaround and it's not pretty (I suppose that's pretty much what point 3 is about) b) it forces a component to be loaded if it is not (that might not be what you actually want) – Piotr Owsiak Oct 13 '15 at 08:45
  • Works really well for what I need. Helps if the component is marked for sync rendering too. I can thus use the callback to wait until the HTML has at least been fetched via requireJS (only happens the first time), and then rely on the sync rendering to ensure the HTML is present in my document when I go access the element. In my case I needed to add an extra child to the component's bound element and that couldn't work originally because KO clears out all child nodes when applying the component binding. – Ian Yates Oct 17 '16 at 07:16