Knockout: получить ссылку на компонент A, чтобы вызвать одну из функций A из компонента B?

Использовался генератор Yeoman's Knockout (приблизительно начало 2015 г.), который включает в себя require.js и router.js. Просто с помощью загрузчика KO.

Я пытаюсь вызвать функцию (ko.observable или нет) в компоненте "a" из компонента "b". Весь пух ниже пытается сделать просто:

// In componentB:

ComponentA.sayFoo();

Читал документы KO по компонентам и загрузчикам, часами взломал и т. д. Я не хочу, чтобы накладные расходы, скажем, postal.js — и также не мог заставить подписки (KO pub/sub) работать — я Полагаю, по той же причине: настроенные таким образом модели представления не имеют ссылок друг на друга (?) - поэтому подписчики в одном модуле не видят издателей в другом (правильно?) (... немного выше моего понимания здесь %-)

1) Это из-за того, что модули не видят друг друга… что этот сгенерированный код не помещает материал KO в глобальное пространство имен?

2) Попытка добраться из одного модуля в другой, кажется, зависит от получения ссылки через параметры обратного вызова, используя функцию ниже, или это неправильно? :

  ko.components.get (name, callback) ;

startup.js с использованием require выглядит следующим образом:

define(['jquery', 'knockout', './router', 'bootstrap', 'knockout-projections'], function($, ko, router) {

// Components can be packaged as AMD modules, such as the following:

   ko.components.register('component-a', { require: 'components/a/component-a' });  
   ko.components.register('component-b', { require: 'components/b/component-b' });  

// [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.]
// [Scaffold component's N/A (I think?)] 

// Start the application
   ko.applyBindings({ route: router.currentRoute });  
});

(Компонент) модуль A прямолинеен, например:

define(['knockout', 'text!./component-a'], function(ko, templateMarkup) {

   function ComponentA (params) { console.log ('CompA'); } ;

   ComponentA.prototype.sayFoo  = function () { console.log ('FOO!'); } ;
   ComponentA.prototype.dispose = function(){};

   return { viewModel: ComponentA, template: templateMarkup };
});

Точно так же модуль B:

define(['knockout', 'text!./component-b'], function(ko, templateMarkup) {

   function ComponentB (params) { console.log ('Compb'); } ;

   ComponentB.prototype.doFoo  = function () { 
     //// B Needs to fire ComponentA.foo() … SEE CODE ATTEMPT BELOW 
   };

   ComponentB.prototype.dispose = function(){};

   return { viewModel: ComponentB, template: templateMarkup };
});

Так вот где я застрял:

  ComponentB.prototype.doFoo  = function () { 
        ko.components.get ('component-a', ( function (parms) {
           console.log ('parms.viewModel : ' + parms.viewModel );  
           // parms.viewModel is (unexpectedly) undefined ! So how to get the ref?
           console.log ('parms.template : ' + parms.template );  
          // does have expected html objects, eg. [object HTMLwhatever], [object HTML...]
 })) ; 

Это должно быть легко, или я тупо упускаю что-то очевидное!?

Может быть, модули нужно определить/настроить по-другому?

Любые предложения помогут! Спасибо


person Howard Pautz    schedule 22.11.2016    source источник


Ответы (2)


Это просто не то, как вы обычно общаетесь между нокаутирующими компонентами.

Ваши варианты:

1) Используйте https://github.com/rniemeyer/knockout-postbox. Это, вероятно, лучший вариант, так как он прекрасно сочетается с нокаутом. Он хорошо документирован, и если у вас возникнут проблемы с его настройкой, вы всегда можете обратиться за помощью сюда.

2) Используйте любой другой глобальный javascript EventBus (например, postal.js) и отправляйте/подписывайтесь на события в ваших компонентах.

3) Пусть ваша корневая ViewModel передает общие наблюдаемые каждому компоненту в качестве параметров - таким образом, каждый компонент может изменять/подписываться на один и тот же наблюдаемый.

4) (Возможно, то, что вы хотите, хотя и худшее решение для масштабирования). Если вы даете идентификаторы различным компонентам, вы можете использовать ko.dataFor(document.getElementById("id")) для прямого доступа к свойствам и методам ваших компонентов.

РЕДАКТИРОВАТЬ: В ответ на комментарий:

Я не смог определить, что/где находится модель корневого представления: ключом является ko.applyBindings({ route: router.currentRoute }), но router.js запутан. Предложения о том, как это определить?

Точно - в вашем случае объект { route: router.currentRoute } ЯВЛЯЕТСЯ вашей корневой ViewModel. В настоящее время у него есть только одно свойство с именем route, но вы определенно можете его расширить.

Например:

var rootViewModel = {
    route: router.currentRoute,
    mySharedObservable: ko.observable('hi!')
}

ko.applyBindings(rootViewModel);

Затем вы можете передать этот наблюдаемый объект нескольким компонентам в качестве параметра следующим образом:

<div id="component-a" data-bind="component: { name: 'component-a', params: {router: $root.router, mySharedObservable: $root.mySharedObservable} }"></div>
<div id="component-b" data-bind="component: { name: 'component-b', params: {router: $root.router, mySharedObservable: $root.mySharedObservable} }"></div>

И, наконец, вы можете использовать новую наблюдаемую из компонента следующим образом:

function ComponentB (params) { 
    this.mySharedObservable = params && params.mySharedObservable;
    console.log(this.mySharedObservable());// This should log 'hi!'
};

Теперь вы можете подписаться на наблюдаемое, изменить его и так далее. Он будет общим для компонентов, поэтому его изменение в одном компоненте вызовет подписку во всех компонентах.

person Bragolgirith    schedule 23.11.2016
comment
thx vtosh - RE 4) - несмотря на то, что он не масштабируется и немного жестко закодирован, это может быть лучше ... Мне нужно всего лишь сделать несколько обменов между компонентами. RE 3) часть проблемы здесь в том, что я использовал стандартный генератор без глубокого понимания всей создаваемой им архитектуры. Я не смог определить, что/где находится модель корневого представления: ключом является ko.applyBindings({ route: router.currentRoute }), но router.js запутан. Предложения о том, как это определить? спасибо еще раз - person Howard Pautz; 23.11.2016
comment
оцените помощь здесь! Не удалось заставить работать версию редактирования, хотя я думаю, что понимаю намерение. 'this.mySharedObservable = параметры && params.mySharedObservable;' дает undefined… так что я предполагаю, что он не передан. Такой синтаксис выглядит странно — почему params && а не просто 'this.mySharedObservable = params.mySharedObservable'? (Я тоже пробовал это, но все еще не определено). - person Howard Pautz; 25.11.2016
comment
update - частично рабочий console.log (sharedOb. через params?+params); читает (... потому что наблюдаемый объект передается напрямую?), но this.myObservable = params; this.myObservable('Фу'); не говорит, что это не функция… хммм - person Howard Pautz; 25.11.2016

Мой стандартный подход состоял бы в том, чтобы контролировать связь через родительскую виртуальную машину.

Родительская виртуальная машина может создать объект подписки[1] и передать его как компоненту A, так и компоненту B в качестве параметра; тогда ComponentA может подписаться на подписку, а ComponentB может инициировать подписку.

//ComponentA
function ComponentA (params) { 
    var shouldSayFoo = params.shouldSayFoo;

    this.shouldSayFooSubscription = shouldSayFoo.subscribe(function () {
        this.sayFoo();
    });
} ;

ComponentA.prototype.sayFoo  = function () { console.log ('FOO!'); } ;
ComponentA.prototype.dispose = function () { this.shouldSayFooSubscription.dispose(); };

//In ComponentB
function ComponentB (params) {
    this._triggerFoo = params.triggerFoo; //Same subscribable as shouldSayFoo in ComponentA
}

ComponentB.prototype.doFoo = function () {
    this._triggerFoo.notifySubscribers(); //notifySubscribers triggers any .subscription callbacks
}

Если ComponentA и ComponentB являются братьями и сестрами, и вы не делаете такие вещи все время, это работает как довольно простое решение. Если компоненты являются «дальними родственниками» или если вы обнаружите, что делаете это много, то я бы предложил какой-то паб-саб. И преимущество этого подхода может быть использовано большим количеством отдельных пар «A-B», не мешая друг другу, что сложнее в системе pub-sub.

[1]: ko.subscribable — это объект с подмножеством наблюдаемой функциональности (ko.observable наследуется от ko.subscribable): он не позволяет вам читать или записывать значения, но позволяет выполнять .subscribe и .notifySubscribers. (По какой-то причине они созданы с помощью new) Вы также можете использовать наблюдаемое, это просто небольшая путаница намерений создать наблюдаемое, если вы не собираетесь хранить в нем значение.

person Retsam    schedule 23.11.2016
comment
thx Retsam. Как я уже упоминал vtosh выше, моя реальная проблема заключалась в использовании стандартного генератора, который я не совсем понял… теперь это немного меня :-0… Я не уверен, что компоненты вообще имеют модель представления в качестве родителя (или я не знаю, где это). Вот почему я не мог заставить работать pub/sub. Тупой. ОК, пытаясь понять код, который вы разместили - у него есть A и B как братья и сестры с подразумеваемой родительской/(корневой) моделью представления, верно? спасибо - person Howard Pautz; 23.11.2016
comment
@HowardPautz Правильно; Я предполагаю, что A и B — братья и сестры и что у них есть родитель. (Что может быть корнем) Если они не являются прямыми братьями и сестрами, должна быть еще какая-то промежуточная проводка, в конечном счете, чтобы эта стратегия сработала, их общий предок должен установить линию связи. - person Retsam; 27.11.2016