Component Architecture
UI Component Tree
<!-- Full UI component structure -->
<listing>
<settings>
<!-- Spinner component for loading state -->
<spinner>listingColumns</spinner>
<!-- Component dependencies -->
<deps>
<dep>listing.listing_columns</dep>
<dep>listing.listing_filters</dep>
</deps>
<!-- External component deps -->
<externalDeps>
<dep>listing.data_source</dep>
</externalDeps>
</settings>
<!-- Data source -->
<dataSource name="listing_data_source">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/data/provider</item>
</item>
</argument>
<settings>
<updateUrl path="mui/index/render"/>
</settings>
<dataProvider class="Magento\Ui\Model\DataProvider" name="listing_data_source">
<settings>
<requestFieldName>id</requestFieldName>
<updateRequestFieldName>ids</updateRequestFieldName>
</settings>
</dataProvider>
</dataSource>
<!-- Listing columns -->
<listingColumns name="listing_columns">
<settings>
<childDefaults>
<provider>listing_data_source</provider>
<dataType>text</dataType>
</childDefaults>
</settings>
</listingColumns>
</listing>
Component Lifecycle
1. Initialization: defaults applied → initObservable → initialize
2. Data loading: dataSource.getData() → components receive data
3. Rendering: template rendered with KnockoutJS bindings
4. Interaction: user actions → observables update → UI re-renders
5. Destruction: component cleanup
Advanced XML Configuration
Component Arguments
<!-- Pass arguments to components -->
<column name="custom">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="dataType" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Custom Column</item>
<item name="sortOrder" xsi:type="string">100</item>
<item name="visible" xsi:type="boolean">true</item>
<item name="sortable" xsi:type="boolean">true</item>
<item name="editable" xsi:type="boolean">false</item>
</item>
</argument>
</column>
Data Mapping
<!-- Map data between components -->
<listing>
<settings>
<dataMapping>
<item name="id" xsi:type="string">entity_id</item>
<item name="label" xsi:type="string">name</item>
</dataMapping>
</settings>
</listing>
Component Listeners
<!-- Listen to other component changes -->
<column name="dependent_column">
<settings>
<imports>
<link name="value">
<link>source.independent_column:value</link>
</link>
</imports>
<exports>
<link name="value">
<link>target_component:value</link>
</link>
</exports>
<listens>
<link name="value">
<link>source_column:value</link>
</link>
</listens>
</settings>
</column>
JavaScript UI Components
Component Class
// app/code/Vendor/Module/view/adminhtml/web/js/component/custom
define([
'Magento_Ui/js/form/element/abstract',
'ko'
], function (Abstract, ko) {
'use strict';
return Abstract.extend({
defaults: {
template: 'Vendor_Module/component/custom',
imports: {
externalValue: '${externalComponent}:value'
},
exports: {
value: '${targetComponent}:value'
},
listens: {
value: 'onValueChange'
}
},
initObservable: function () {
this._super()
.observe({
value: null,
customProp: false
});
return this;
},
initialize: function () {
this._super();
this.value.subscribe(this.onChange.bind(this));
return this;
},
onValueChange: function (newValue) {
console.log('Value changed:', newValue);
},
getValue: function () {
return this.value();
},
setValue: function (val) {
this.value(val);
}
});
});
Component Template
<!-- Vendor/Module/view/adminhtml/web/template/component/custom.html -->
<div class="custom-component" data-bind="visible: visible()">
<label data-bind="text: label"></label>
<input type="text" data-bind="value: value, attr: { placeholder: placeholder }">
<span data-bind="text: value, visible: displayValue()"></span>
</div>
Data Providers
Custom Data Provider
namespace Vendor\Module\Ui\DataProvider;
class CustomDataProvider extends \Magento\Ui\Model\DataProvider
{
public function __construct(
string $name,
string $primaryFieldName,
string $requestFieldName,
\Magento\Framework\Db\Select $select,
array $meta = [],
array $data = []
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, [], $meta, $data);
$this->setSelect($select);
}
public function getData(): array
{
$items = $this->getCollection()->getItems();
$result = [];
foreach ($items as $item) {
$result[$item->getId()] = $item->getData();
}
return [
'totalRecords' => $this->getCollection()->getSize(),
'items' => $result,
];
}
public function getMeta(): array
{
$meta = parent::getMeta();
$meta['listing_columns']['children']['custom_field']['arguments']['data']['config'] = [
'dataType' => 'text',
'formElement' => 'input',
'component' => 'Magento_Ui/js/form/element/text',
'label' => __('Custom Field'),
];
return $meta;
}
}
Data Provider Configuration
<dataSource name="custom_source">
<settings>
<deps>
<dep>custom_source</dep>
</deps>
<updateUrl path="mui/index/render"/>
</settings>
<dataProvider class="Vendor\Module\Ui\DataProvider\CustomDataProvider" name="custom_source">
<settings>
<requestFieldName>id</requestFieldName>
<updateRequestFieldName>ids</updateRequestFieldName>
<filterUrlParams>
<param name="id">entity_id</param>
</filterUrlParams>
</settings>
</dataProvider>
</dataSource>
Quiz
1. What is the UI component lifecycle?
2. What do imports/exports do in XML config?
3. What must a data provider implement?
Flashcards
Question
UI component lifecycle?
Click to reveal answer
Answer
defaults → initObservable → initialize → load data → render
Question
imports/exports purpose?
Click to reveal answer
Answer
Auto-sync data between UI components
Question
Data provider getData() returns?
Click to reveal answer
Answer
['totalRecords' => n, 'items' => [...]]
Question
initObservable() purpose?
Click to reveal answer
Answer
Initialize observable properties for reactive UI
Revision Notes
Key Takeaways
- 1. UI components follow a defined lifecycle with initialization and data loading
- 2. XML configuration supports args, imports, exports, and listeners
- 3. JavaScript components extend abstract classes and use KnockoutJS observables
- 4. Data providers supply data via getData() with totalRecords and items
- 5. Components can sync data automatically via imports/exports
Interview Tips
- • Explain the full component lifecycle from XML to rendered UI
- • Describe how components communicate via imports/exports
- • Discuss data provider architecture and getData() contract
Cheat Sheet
UI Components:
XML: <listing>/<form> root
Config: args, imports, exports, listeners
JS: extend abstract, initObservable
Data: getData() → totalRecords + items
Lifecycle:
defaults → initObservable → initialize → render
Sync:
imports: receive from other components
exports: send to other components
listens: react to changes