7

In many of Magento 2's UI Element view model constructors, the defaults array will have an imports or exports property.

return Collection.extend({
    defaults: {
        //...
        imports: {
            rows: '${ $.provider }:data.items'
        },

return Insert.extend({
    defaults: {
        //...
        exports: {
            externalFiltersModifier: '${ $.externalProvider }:params.filters_modifier'
        },

Looking at the source of the uiElement module,

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js
    initLinks: function () {
        return this.setListeners(this.listens)
                   .setLinks(this.links, 'imports')
                   .setLinks(this.links, 'exports')
                   .setLinks(this.exports, 'exports')
                   .setLinks(this.imports, 'imports');
    },

These imports/export seem to have something to do with "linking" information between objects when an object is instantiated. However, it's not clear how this linking works (uiRegistry based?) or what the syntax for strings like ${ $.provider }:data.items are. It's clear these strings use template literals that expand into something like

foo_bar:data.items

But the meaning of this final string is still mysterious.

Does anyone know how the import/export properties of these objects work?

18

These properties allow components to be connected so they can interact with each other. The principle is somewhat simple: import (take) a value from another component or export (send) a value to a different component or do both.


Note: to maintain clarity in this answer, a "component" is a Javascript object that is returned by RequireJS, has a specific name, and can be accessed by that name via the UIRegistry.

In addition, all examples below would be inside the defaults: {} property of the component.


With the principle laid out, let's start with what I consider the easiest concept:

Imports

This property takes a value from another component and assigns it to the property specified. In the following example, we declare an import:

imports: {
    message: '${ $.provider }:data.message'
}

When Magento initializes this component, it will attempt to assign the value to the message property. This property will be available in KnockoutJS context. As we know, however, it will evaluate the imports.message value as a template literal expression first. In this case, Magento will parse the $.provider and should obtain a value. While that could be any number of things, in this example and according to many of Magento's core use cases, it is the name of a component that is in the UI registry. That will be parsed before the next step.

Since the message property is in the imports property, it will be passed to the setLinks() method in uiElement.initLinks(). The setLinks() method is in Magento/Ui/view/base/web/js/lib/core/element/links.js. There, it loops over all properties (only message here) in the object that was passed in (imports in this case). On those properties, it will attempt to transfer data from one component to the other.

The transfer() function is the next place of interest. Here, the registry is searched for the component that is the "owner", in the case of an import. This component is the one that currently "owns" or has the data and would be the $.provider in the above example. If the component is found, it will then proceed to link the data with the setLink() function.

There are two things of note in that method: first, it sets an event listener on the property, and second, it will immediately transfer the data if the applicable flag has been sent. In my testing, it always passed the immediate parameter so the transfer happened during initialization. However, because of the event listener that was attached in the first step, it will continue to update values, if they change, so that both components stay in sync.

The data is then set on (or, in simpler terms: "returned to the") component that had the imports: {} property. As I mentioned earlier, it is then assigned directly to the component's property that declared it - essentially this.message in the above example and not this.defaults.imports.message. As a result, data-bind="text: message should display the value returned from the linked component's data.message property.

This approach allows you to define what the property name is in the source component. In the above example, you could use alertMessage: ... instead of message as your component's property name.

Exports

Exports are the inverse of imports. They are based on the same functionality as imports, but instead of taking data from one component and assigning it to itself, it sends its own data to another component. As a result, nearly everything is the opposite. Take this example:

exports: {
    phoneNumber: '${ $.contactForm }:phone'
}

In this example, setLinks() takes the value of this component's phoneNumber property and assigns it to the contact form's phone property. It is the same as explicitly declaring a phone property in the $.contactForm component. Without any particular set up in the $.contactForm, you can then access this data directly. Perhaps like this in a Knockout template: data-bind="text: phone.

Links

Finally, the links property is the same as declaring both imports and exports for the same property. At first glance, this may seem like a circular reference. While it is that in a way, there are times where this could be helpful. While I'm sure there are many more use cases, the one I can see is the ability for one component to manipulate data from another component dynamically. In this case, ComponentA is the source of some data and displays that on the page. ComponentB needs to manipulate that data and so it links to that property. It can both display the data and manipulate the actual data in ComponentA without ever extending or changing ComponentA.

One thing to note, though, is that, by default, links are not a way to connect two other modules. In other words, ComponentC cannot link ComponentA to ComponentB. It is a method of bi-directionally syncing one component with another.


Linking (imports, exports, and links) can almost always facilitate functions assigned to those properties as well. I ran into some strange behavior while creating observables and using links but overall it worked quite well.

The linking provides values that are available in KnockoutJS scope and can by manipulated as you would any other property. And, to reiterate clearly: remember that the imports, exports, and links object's keys always refer to properties of the current component (the one in which those properties were declared), while the value pertains to the name and properties of the remote component.


In conclusion, Magento uses this linking functionality to connect different components with each other and it is a way that we can access, provide, or sync data with other components.

  • Great explanation @bassplayer7 - Do you have any examples of this? I understand how it works but for the life of me I can't get two of my simple UI components to share a KO observable. – Ben Crook Dec 19 '17 at 17:13
  • @BenCrook, unfortunately, I haven't done much with this for awhile so I don't have one right off. By now I'm sure you have figured it out, but I do suggest working with imports and exports rather than links. I have found links to be more obscure and brittle. If you did come across an example, could you share a link? – bassplayer7 Feb 6 '18 at 20:23
  • I tried and failed to get imports/exports/links working, Vinai has answered the question here although I've not had chance to try it out yet. – Ben Crook Feb 7 '18 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.