Component Communication in Knockout.JS


Mathias Mamsch

I'm designing a "medium" sized app in KnockoutJS and I'd like to know how to send events between components.

Imagine a nested component hierarchy in KnockoutJS:

Root Viewmodel -> A -> B -> C
               -> D

How does D respond to the message from C? The obvious way to knock out JS is to have C write the observable passed as a parameter, and share that observable with D, which reacts to changes in the observable.

What I don't like about this approach is that A and B need to know about the message, while A and B actively forward the handler with their parameters. Using normal dependency injection methods, I can connect components C and D directly to each other, e.g. inject D into C without A and B knowing.

So my question is:

  • Is there a way to manually wire components inside the root view model (eg by intercepting component creation)?

or rewrite:

  • How can I configure nested components from the main viewmodel without looking at the parent component?

ko.components.register("aaa", {
    viewModel: function (params) { this.handler = params.handler;  },
    template: "<span>A</span> <bbb params='handler: handler'></bbb>"
});

ko.components.register("bbb", {
    viewModel: function (params) { this.handler = params.handler;  },
    template: "<span>B</span> <ccc params='handler: handler'></ccc>"
});

ko.components.register("ccc", {
    viewModel: function (params) { this.handler = params.handler;  },
    template: "<span>C</span> <button data-bind='click: handler'>OK</button>"
});

ko.components.register("ddd", {
    viewModel: function (params) { 
        var self = this; 
        this.text = ko.observable("No event received!"); 
        if (params.onClick)     params.onClick.subscribe(function () {
            self.text("Event Received!");
        });
    },
    template: "<span>D</span> <span data-bind='text:text'/>"
});


ko.applyBindings({
    onClick: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<p><aaa params='handler: onClick'> </aaa></p>
<p><ddd params='onClick: onClick'> </ddd></p>

username

Is there a way to manually wire components inside the root view model (eg by intercepting component creation )?

(emphasis mine)

knockout mechanism

You can inject a custom component loader by adding ko.components.loadersany combination of methods to an array of objects getConfig, loadComponent, , loadTemplate, and loadViewModel.

Since you're creating connections between view models, this is the only method we need to define. From the documentation:

loadViewModel(name, viewModelConfig, callback)

viewModelConfig values ​​are just viewModel properties in any componentConfig object. For example, it could be a constructor, ...

this method

Your component is defined as a direct reference to the constructor. We'll wrap this constructor in a factory function that replaces what was passed to the component paramswith a concatenated sharedParamsobject that holds everything passed to any component on the chain. Whether this is "safe" enough is up to you. It should be easy to have another way to connect aaa, dddonce you have a custom loader you should be fine.

In short, our custom loader will:

  1. Retrieves the component's viewmodel's original constructor ( VM)
  2. Dynamically create a function factorythat :
    • Add the passed content paramsto the binding context
    • VMConstruct an instance with shared parameters
    • return new viewmodel
  3. call the newly created factoryfunction

In code:

ko.components.loaders.unshift({
  loadViewModel: function loadViewModel(name, VM, callback) {
    const factory = function (params, componentInfo) {
      const bc = ko.contextFor(componentInfo.element);

      // Overwrite sharedParams
      bc.sharedParams = Object.assign(
        { },
        bc.sharedParams || {},
        params
      );

      return new VM(bc.sharedParams);
    };

    return callback(factory);
  }
});

Run the example

Check out the fiddle of this link and play with the custom loader yourself. I've included several nested component structures to show that paramsmanual passing is no longer necessary .

https://jsfiddle.net/sqLathu9/

Related


Component Communication in Knockout.JS

Mathias Mamsch I'm designing a "medium" sized app in KnockoutJS and I'd like to know how to send events between components. Imagine a nested component hierarchy in KnockoutJS: Root Viewmodel -> A -> B -> C -> D How does D respond to the message

Communication between nested components in Knockout.js

Bauzy I'm trying to use knockout custom elements in my application and I'm running into an issue. I have two nested custom elements and I want them to communicate. I'm trying to share observables between them, but I keep getting an error: Unable to process bin

Communication between nested components in Knockout.js

Bauzy I'm trying to use genotyping custom elements in my app, but I'm running into a problem. I have two nested custom elements and I want them to communicate. I'm trying to share observables between them, but I keep getting an error: Unable to process binding

Communication between nested components in Knockout.js

Bauzy I'm trying to use genotyping custom elements in my app, but I'm running into a problem. I have two nested custom elements and I want them to communicate. I'm trying to share observables between them, but I keep getting an error: Unable to process binding

Communication between nested components in Knockout.js

Bauzy I'm trying to use genotyping custom elements in my app, but I'm running into a problem. I have two nested custom elements and I want them to communicate. I'm trying to share observables between them, but I keep getting an error: Unable to process binding

Knockout.js: Local Observables in Component ViewModels

Steve Johnstone I'm using a knockout component (loaded via require.js) to create a login widget. Javascript: ko.components.register('login-widget', { viewModel: { require: '/components/login-widget.js' }, template: { require: 'text!/components/login-wi

Knockout.js component parameter undefined

Arnold Wiersma I am creating a Knockout js component that can be used in a notification system. When I add a component to the page I get a params is undefined message, it looks like my params object is not sent to the viewmodel's constructor. At first I though

Component communication in Vue.js not rendering correctly

Casper In parent component I have: <todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index"> </todo-item> It just iterates over the todosarray and gets each object todo objectand passes propseach Object and its index to the chil

Component communication in Vue.js not rendering correctly

Casper In parent component I have: <todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index"> </todo-item> It just iterates over the todosarray and gets each object todo objectand passes propseach Object and its index to the chil

Component communication in Vue.js not rendering correctly

Casper In parent component I have: <todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index"> </todo-item> It just iterates over the todosarray and gets each object todo objectand passes propseach Object and its index to the chil

Component communication in Vue.js not rendering correctly

Casper In parent component I have: <todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index"> </todo-item> It just iterates over the todosarray and gets each object todo objectand passes propseach Object and its index to the chil

Angular2 component communication in JS

Prachil Tambe I can't communicate between two components in Angular 2 using JS. Here is the link to the tutorial. I think I am missing something in the directive. I also tried to change it by declaring This is app.main.js (function () { var Component = ng.cor

Component communication in Vue.js not rendering correctly

Casper In parent component I have: <todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index"> </todo-item> It just iterates over the todosarray and gets each object todo objectand passes propseach Object and its index to the chil

Knockout.js: How to get registered component instance?

Oles Turco How can I get an instance of a registered component via knock.js? Specifically, for a component like this: ko.components.register('create-feature-request', { viewModel: function(params) { var self = this; self.title = ko.observab

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: How to get registered component instance?

Oles Turco How can I get an instance of a registered component via knock.js? Specifically, for a component like this: ko.components.register('create-feature-request', { viewModel: function(params) { var self = this; self.title = ko.observab

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