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.