How does the Knockout JS component viewmodel function work as a callback?


Renato Xavier

According to the Knockout documentation here , the component viewModel is only instantiated "on demand", considered declared, for example:

<div data-bind='component: {
    name: componentNameObservable,
    params: { mode: "detailed-list", items: productsList }
}'></div>

Consider the scenario of a single page application with a routing mechanism such as SammyJS, Crossroads.js or any other routing mechanism.

When a route change request matches a route pattern, the route library handler that normally causes the route match event to set a new value for componentNameObservable. This will trigger the injection of the new component into the bound element. On top of that, also in the handler for that route match, I want to execute a function declared in the bound component viewModel to refresh/set the viewmodel data in response to the route change event. How is that possible? Since the component viewModel instantiation in these cases is controlled by Knockout's internal component binding mechanism, I don't have access to its functionality, so I can reference them as callbacks to execute. If possible, I would pass the component viewModel function as a callback parameter to the routing library route matching handler,

I am using CommonJS to register components for use with Browserify as described here

http://knockoutjs.com/documentation/component-loaders.html#note-integrating-with-browserify

In this case, module.exports on the component's view model must expose its entire uninstantiated constructor, so that any require('myViewModel') is converted to a bound model and the view model can be "updated" correctly. element.

Any suggestions?

Chotabe

Suppose I want to execute a function declared inside a bound component viewModel to refresh/set viewmodel data in response to a route change event. How is that possible?

In the component registration document you have 4 different options to provide the view model:

  • First: Constructor
  • Second: Shared Object Instances
  • Third: createViewModel factory function
  • Fourth: AMD module whose value describes a view model

In fact, the fourth option returns one of the other three using the AMD module. So there are only 3 possible options (use or not) require.

If your SPA only uses a single instance of the component, you can use the second solution: create a view model, store it in a variable that is always in scope (e.g. in the global scope or inside a module), then Register the component to use it. This way, when the component is instantiated by the routed event, you can access the view model via variables to call the desired functionality.

If your SPA can have multiple different instances of the component, or you just don't want to use the previous solution, you have to use the 1st or 3rd option (it doesn't matter which one is for this matter). In this case, you can pass a callback function to your component, which will be available in the constructor (first option) or factory method (third option) parameter. A constructor (or factory) can call this callback to expose its functionality. You can implement something like this, but not necessarily exactly:

in the main scope of your application

// Here you'll store the component APIs to access them:
var childrenComponentsApi = {};
// This will be passed as a callback, so that the child component
// can register the API
var registerChildComponentApi = function(api) {
    childrenComponentsApi.componentX = api;
};

Note: It's important to have an object in which you can register functions so you don't lose references

In the view:

<div data-bind='component: {
  name: 'componentX',
  params: { registerApi: registerChildComponentApi, /*other params*/ }
}'></div>

In the constructor (or factory) body of the component's view model:

params.registerApi({ // The callback is available in thereceived params
   func1: func1,  // register the desired functions
   func2: func2}); 

Later, in the main scope, you can access the functionality of the component like this:

childrenComponentsApi.componentX.fun1(/ *参数* /);

This is not fully valid code, but I hope it gives you an idea of ​​how to achieve what you want.

This solution works well if the API is not called immediately. I use this implementation to ensure that the component has been instantiated when the function will be called by the user's action.

However, in your case, the creation of the component is asynchronous, so you have to modify the implementation. There are at least two possible ways:

1) It is easier to change the registerApiimplementation and use it for initialization. like this:

In the viewmodels constructor:

params.registerApi({
   init: init,    // initialization function
   func1: func1,  // other exposed functionality
   func2: func2}); 

In the main scope:

var registerChildComponentApi = function(api) {
    childrenComponentsApi.componentX = api;
    childrenComponentsApi.componentx.init(/* params*/)
}

In this implementation , the implementation initis called after the client component has run the callback , so the component is guaranteed to be available.

2) A more complex solution involves using promises. If your component needs to do something asynchronous to get ready (for example, make an AJAX call), you can make it return all promises outside of the exposed API, so that the component can resolve the promise when it's actually ready, and the main scope Can run only provide API functionality when the promise has been resolved. like this:

In the view model constructor:

params.registerApi({
   ready: ready,  // promise created and solved by the component
   func1: func1,  // exposed functionality
   func2: func2}); 

In the main scope:

var registerChildComponentApi = function(api) {
    childrenComponentsApi.componentX = api;
}
childrenComponentsApi.componentx.ready().then(
   childrenComponentsApi.componentx.func1;
);

These are some implementation examples, but many variations are possible. For example, if you only need to run the functionality of the initcomponent's viewmodel , you can provide similar parameters to the provideInitcomponent and then run it in the constructor by pasting the component's parameters init. like this:

<div data-bind='component: {
  name: 'componentX',
  params: { provideInit: provideInit, /*other params*/ }
}'></div>

var proviedInit: function(init) {
   init(/* params */);
};

And don't forget, the component's view model can also be initialized by passing all required parameters to the constructor, or even by passing observables as parameters and modifying them from the main scope.

The last best advice I can give you is to properly standardize and document the functionality registerApiso that all components are implemented and used the same way.

As I said at the beginning, these solutions can all be implemented directly or using AMD modules. That is, you can register components that provide the viewmodel constructor directly (the first option), or use the fourth option to define the constructor as an AMD module.

Related


How does callback function work in ajaxcall in meteor?

Facebook Facebook logo Sign up for Facebook to connect with Zaik Chohan I am calling some third party API. Because I have to call back some functions in response to some functions. I can't get the callback to succeed, but it runs successfully when called by na

How does callback function work in ajaxcall in meteor?

Zaik Chohan I am calling some third party API. Because I have to call back some functions in response to some functions. I can't get the callback to succeed, but when I call it by name it runs successfully. I'm using callbacks to generalize it, so whenever I w

How does this callback function example work?

jesung1221 (I'm asking other programmers to understand that I'm re-studying nodeJS after my army. I'm a beginner, maybe the question is too simple, but please help me understand the sample code below) function add(a, b, callback) { var result = a + b;

How does the GroceryCRUD callback function work?

Bernal Carlos I need to make some customizations to CRUD. Basically, I need to upload multiple files in an "add" or "edit action" and all I want to save in the database is the url of the folder containing the images. I've been searching the forums and it seems

How does callback function work in ajaxcall in meteor?

Zaik Chohan I am calling some third party API. Because I have to call back some functions in response to some functions. I can't get the callback to succeed, but when I call it by name it runs successfully. I'm using callbacks to generalize it, so whenever I w

How does this callback function example work?

jesung1221 (I'm asking other programmers to understand that I'm re-studying nodeJS after my army. I'm a beginner, maybe the question is too simple, but please help me understand the sample code below) function add(a, b, callback) { var result = a + b;

Knockout JS calling ViewModel function in foreach binding

Luis Manez - Microsoft MVP Let's consider using a culled view model like this: var data = [{ id: 1, name: "John Doe" }, { id: 2, name: ""}, { id: 3, name: "Peter Parker"}]; var viewModel = { items: ko.observableArray(data) }; viewModel.showName = functio

Knockout: How to call a function of another viewmodel

Vikas Khartude I am using knockout.js with asp.net web api. I have created functionality related to messaging. I created two separate viewmodels, one for sending messages and the other for displaying messages. I am facing the problem of implementing the follow

Knockout: How to call a function of another viewmodel

Vikas Khartude I am using knockout.js with asp.net web api. I have created functionality related to messaging. I created two separate viewmodels, one for sending messages and the other for displaying messages. I am facing the problem of implementing the follow

Knockout.js callback whenever a component is loaded into the DOM

Havardu I am writing a single page application using kickout.js. I want all textboxes in the application to behave in a certain way, for example, to select all the text currently in the set. To avoid duplicate solutions like adding custom bindings to all input

Knockout.js callback whenever a component is loaded into the DOM

Havardu I am writing a single page application using kickout.js. I want all textboxes in the application to behave in a certain way, for example, to select all the text currently in the set. To avoid duplicate solutions like adding custom bindings to all input

Knockout.js callback whenever a component is loaded into the DOM

Havardu I am writing a single page application using kickout.js. I want all textboxes in the application to behave in a certain way, for example, to select all the text currently in the set. To avoid duplicate solutions like adding custom bindings to all input