let's say I want to execute a function declared inside the associated viewModel component, update / configure model model data, responding to an event with a changed route. How is this possible?
In the documentation for registering components , you have 4 different options for presenting a presentation model:
- 1st: constructor function
- 2nd: shared object instance
- 3rd: createViewModel factory function
- 4th: AMD module whose value is described by the view model
In fact, option 4 uses the AMD module to return one of the other three options. Thus, there are only 3 possible options: with or without require .
If your SPA will use only one instance of your component, you can use the second solution: create a view model, save it in a variable that is always in scope (for example, in the global scope or inside the module) and register the component to use it. Thus, when a component is initialized by a routing event, you can access the view model through a variable to invoke the required functionality.
If your SPA may have several different instances of your component, or you simply do not want to use the previous solution, you should use the 1st or 3rd option (it does not matter which one is relevant to this issue). In this case, you can pass a callback function to your component, which will be available in the constructor parameters (option 1) or factory (option 3). A constructor (or factory) can call this callback to show its functionality. You can implement something like this, but not necessarily exactly like this:
In the main area of your application
NOTE: it is important to have an object where you can register functionality so that you do not lose the link
In view:
<div data-bind='component: { name: 'componentX', params: { registerApi: registerChildComponentApi, /*other params*/ } }'></div>
In the constructor of the component view model (or factory):
params.registerApi({
Later, in the main area, you can access component functions such as this:
childrenComponentsApi.componentX.fun1 (/ * params * /);
This is not really working code, but I hope that it will give you an idea of how to implement what you need.
This solution works fine if the API is not called immediately. I used this implementation when the functionality is called by the user action, so I'm sure that the component is already installed.
But in your case, creating the component is asynchronous, so you need to change the implementation. There are at least two possible ways:
1) The easiest way is to change the implementation of registerApi and use it for initialization. Something like that:
In the viewmodels constructor:
params.registerApi({ init: init, // initialization function func1: func1, // other exposed functionality func2: func2});
In the main area:
var registerChildComponentApi = function(api) { childrenComponentsApi.componentX = api; childrenComponentsApi.componentx.init() }
In this implementation, init is called after the callback has been launched by the client component, so you can be sure that this component is available.
2) a more complex solution involves the use of promises. If your component needs to perform asynchronous operations for preparation (for example, for AJAX calls), you can force it to return the promise, in addition to all open APIs, so that the component resolves the promise when it is really ready, and the main area starts the API only when the promise has been resolved . Something like that:
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 area:
var registerChildComponentApi = function(api) { childrenComponentsApi.componentX = api; } childrenComponentsApi.componentx.ready().then( childrenComponentsApi.componentx.func1; );
These are some implementation examples, but there can be many. For example, if you only need to run the init function for the component representation model, you can specify the component, for example provideInit , to the component and run it in a constant, parsing the init component. Something like that:
<div data-bind='component: { name: 'componentX', params: { provideInit: provideInit, /*other params*/ } }'></div> var proviedInit: function(init) { init(); };
And do not forget that you can also initialize the component representation model by passing all the necessary parameters to the constructor. Or even passing observables as parameters and changing them from the main area.
The last best advice I can give you is to standardize and properly document registerApi functionality so that all components are implemented and used the same way.
As I said at the beginning, any of these solutions can be implemented directly or using AMD modules. That is, you can register the component that provides the constructor of the view model directly (option 1), or define the constructor as an AMD module and use option 4.