diff --git a/Astrio/Task529/Block/CustomBlock.php b/Astrio/Task529/Block/CustomBlock.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8d67c90cdd077596a15d4cae1eb5792c051ba7b
--- /dev/null
+++ b/Astrio/Task529/Block/CustomBlock.php
@@ -0,0 +1,103 @@
+<?php
+namespace Astrio\Task529\Block;
+
+
+/**
+ * Class Test
+ * @package Astrio\KnockoutJs\Block
+ */
+class CustomBlock extends \Magento\Framework\View\Element\Template
+{
+    /** @var array|\Magento\Checkout\Block\Checkout\LayoutProcessorInterface[] */
+    protected $layoutProcessors;
+
+
+    /** @var \Magento\Framework\Serialize\Serializer\Json */
+    protected $serializer;
+
+
+    /** @var array|mixed */
+    protected $jsLayout;
+
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Category\Collection
+     */
+    protected $categoryCollection;
+
+    /**
+     * @var \Magento\Catalog\Model\Category
+     */
+    protected $categoryObject;
+
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
+     */
+    protected $productCollectionFactory;
+
+    /**
+     * @param \Magento\Framework\View\Element\Template\Context $context
+     * @param \Magento\Framework\Serialize\Serializer\Json $serializer
+     * @param array $layoutProcessors
+     * @param array $data
+     * @param \Magento\Catalog\Model\Category $categoryObject
+     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magento\Framework\Serialize\Serializer\Json $serializer,
+        array $layoutProcessors = [],
+        array $data = [],
+        \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection,
+        \Magento\Catalog\Model\Category $categoryObject,
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+    ) {
+        parent::__construct($context, $data);
+        $this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ?
+
+            $data['jsLayout'] : [];
+        $this->layoutProcessors = $layoutProcessors;
+        $this->serializer = $serializer;
+        $this->categoryCollection = $categoryCollection;
+        $this->categoryObject = $categoryObject;
+        $this->productCollectionFactory = $productCollectionFactory;
+    }
+
+
+    /** @return string */
+    public function getJsLayout()
+    {
+        foreach ($this->layoutProcessors as $processor) {
+            $this->jsLayout = $processor->process($this->jsLayout);
+        }
+        return $this->serializer->serialize($this->jsLayout);
+    }
+
+    /**
+     * @return array
+     */
+    public function getCategories()
+    {
+        $productsCollection = $this->productCollectionFactory->create()
+            ->addAttributeToFilter('is_collection', '1')
+            ->load();
+
+        $categories = [];
+        foreach ($productsCollection as $product) {
+            if ($product->getCategoryIds()) {
+                $categories = array_merge($categories, $product->getCategoryIds());
+            }
+        }
+
+        $categories = array_unique($categories);
+        $resultCategoriesArray = [];
+
+        $this->categoryCollection->addAttributeToSelect(['entity_id', 'name'])
+            ->addAttributeToFilter('entity_id', ['in' => $categories]);
+        $this->categoryCollection->load();
+
+        foreach ($this->categoryCollection as $category) {
+            $resultCategoriesArray[] = array('value' => $category->getId(), 'name' => $category->getName());
+        }
+        return $resultCategoriesArray;
+    }
+}
diff --git a/Astrio/Task529/Controller/Ajax/Products.php b/Astrio/Task529/Controller/Ajax/Products.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b49f01ee5610d01b9797badce811b1b54521cb6
--- /dev/null
+++ b/Astrio/Task529/Controller/Ajax/Products.php
@@ -0,0 +1,97 @@
+<?php
+namespace Astrio\Task529\Controller\Ajax;
+
+
+/**
+ * Class Product
+ * @package Astrio\KnockoutJs\Controller\Module9
+ */
+class Products extends \Magento\Framework\App\Action\Action
+{
+    /**
+     * @var \Magento\Customer\Model\Session
+     */
+    protected $customerSession;
+
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
+     */
+    protected $productCollectionFactory;
+
+    /**
+     * @var \Magento\Catalog\Helper\Image
+     */
+    protected $helperImport;
+
+    /**
+     * @param \Magento\Framework\App\Action\Context $context
+     * @param \Magento\Customer\Model\Session $customerSession
+     * @param \Magento\Framework\Controller\ResultFactory $resultFactory
+     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+     * @param \Magento\Catalog\Helper\Image $helperImport
+     */
+    public function __construct(
+        \Magento\Framework\App\Action\Context $context,
+        \Magento\Customer\Model\Session $customerSession,
+        \Magento\Framework\Controller\ResultFactory $resultFactory,
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
+        \Magento\Catalog\Helper\Image $helperImport
+    ) {
+        parent::__construct($context);
+        $this->customerSession = $customerSession;
+        $this->resultFactory = $resultFactory;
+        $this->productCollectionFactory = $productCollectionFactory;
+        $this->helperImport = $helperImport;
+    }
+
+    /**
+     * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $params = $this->getRequest()->getParams();
+
+        $productsCollection = $this->productCollectionFactory->create()
+            ->addAttributeToSelect(['name', 'description', 'price', 'small_image', 'is_collection'])
+            ->addAttributeToFilter('is_collection', '1')
+            ->addCategoriesFilter(['in' => $params['categoryid']]);
+
+        if (isset($params['sortby'])) {
+            switch ($params['sortby']) {
+                case 'asc_price':
+                    $productsCollection->addAttributeToSort('price', 'ASC');
+                    break;
+                case 'desc_price':
+                    $productsCollection->addAttributeToSort('price', 'DESC');
+                    break;
+                case 'asc_name':
+                    $productsCollection->addAttributeToSort('name', 'ASC');
+                    break;
+                case 'desc_name':
+                    $productsCollection->addAttributeToSort('name', 'DESC');
+                    break;
+            }
+        }
+
+
+        $productsCollection->load();
+
+        $productsArray = [];
+        foreach ($productsCollection as $product) {
+            $productsArray[] = [
+                'name' => $product->getName(),
+                'description' => $product->getDescription(),
+                'price' => $product->getPrice(),
+                'image' => $this->helperImport->init($product, 'product_page_image_small')
+                    ->setImageFile($product->getSmallImage())
+                    ->resize(380)
+                    ->getUrl()
+            ];
+        }
+
+        $resultJson = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_JSON);
+        $resultJson->setData($productsArray);
+        return $resultJson;
+
+    }
+}
diff --git a/Astrio/Task529/Controller/Index/Index.php b/Astrio/Task529/Controller/Index/Index.php
new file mode 100644
index 0000000000000000000000000000000000000000..a2543ab61711bb359aaeb6d3521b83bbf563f9b4
--- /dev/null
+++ b/Astrio/Task529/Controller/Index/Index.php
@@ -0,0 +1,17 @@
+<?php
+namespace Astrio\Task529\Controller\Index;
+
+/**
+ *
+ */
+class Index extends \Magento\Framework\App\Action\Action
+{
+    /** @return \Magento\Framework\View\Result\Page */
+    public function execute()
+    {
+        /** @var \Magento\Framework\View\Result\Page $resultPage */
+        $resultPage = $this->resultFactory
+            ->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
+        return $resultPage;
+    }
+}
diff --git a/Astrio/Task529/Setup/Patch/Data/AddIsCollectionAttribute.php b/Astrio/Task529/Setup/Patch/Data/AddIsCollectionAttribute.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca39aabf169880e27c69b4a335e983954fec01fc
--- /dev/null
+++ b/Astrio/Task529/Setup/Patch/Data/AddIsCollectionAttribute.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Astrio\Task529\Setup\Patch\Data;
+
+use Magento\Eav\Setup\EavSetup;
+use Magento\Eav\Setup\EavSetupFactory;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Framework\Setup\Patch\DataPatchInterface;
+
+class AddIsCollectionAttribute implements DataPatchInterface {
+    /** @var ModuleDataSetupInterface */
+    private $moduleDataSetup;
+
+    /** @var EavSetupFactory */
+    private $eavSetupFactory;
+
+    /**
+     * @param ModuleDataSetupInterface $moduleDataSetup
+     * @param EavSetupFactory $eavSetupFactory
+     */
+    public function __construct(
+        ModuleDataSetupInterface $moduleDataSetup,
+        EavSetupFactory $eavSetupFactory
+    ) {
+        $this->moduleDataSetup = $moduleDataSetup;
+        $this->eavSetupFactory = $eavSetupFactory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function apply()
+    {
+        /** @var EavSetup $eavSetup */
+        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
+
+        $eavSetup->addAttribute(\Magento\Catalog\Model\Product::ENTITY, 'is_collection', [
+            'group' => 'General',
+            'type' => 'int',
+            'backend' => '',
+            'frontend' => '',
+            'input' => 'select',
+            'label' => 'Is Collection',
+            'source' => \Magento\Catalog\Model\Product\Attribute\Source\Boolean::class,
+            'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
+            'visible' => true,
+            'required' => false,
+            'default' => '0',
+            'searchable' => false,
+            'filterable' => false,
+            'comparable' => false,
+            'visible_on_front' => true,
+            'used_in_product_listing' => true,
+            'unique' => false,
+        ]);
+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function getDependencies()
+    {
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAliases()
+    {
+        return [];
+    }
+
+}
diff --git a/Astrio/Task529/etc/frontend/routes.xml b/Astrio/Task529/etc/frontend/routes.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f95811816375dea55aa0e8bfb6726fc923f449d0
--- /dev/null
+++ b/Astrio/Task529/etc/frontend/routes.xml
@@ -0,0 +1,7 @@
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
+    <router id="standard">
+        <route id="collection" frontName="collection">
+            <module name="Astrio_Task529"/>
+        </route>
+    </router>
+</config>
diff --git a/Astrio/Task529/etc/module.xml b/Astrio/Task529/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d9bcd142e1ac98ad17767a040918528853085644
--- /dev/null
+++ b/Astrio/Task529/etc/module.xml
@@ -0,0 +1,3 @@
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:fraVendor_Modulemework:Module/etc/module.xsd">
+    <module name="Astrio_Task529" setup_version = "1.0.0"/>
+</config>
diff --git a/Astrio/Task529/registration.php b/Astrio/Task529/registration.php
new file mode 100644
index 0000000000000000000000000000000000000000..a50cd91e2b0e0c2826710bca4301971c3c92f85a
--- /dev/null
+++ b/Astrio/Task529/registration.php
@@ -0,0 +1,6 @@
+<?php
+\Magento\Framework\Component\ComponentRegistrar::register(
+    \Magento\Framework\Component\ComponentRegistrar::MODULE,
+    'Astrio_Task529',
+    __DIR__
+);
diff --git a/Astrio/Task529/view/frontend/layout/collection_index_index.xml b/Astrio/Task529/view/frontend/layout/collection_index_index.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6248467c01844714a2c73fe33f6db25c488938bf
--- /dev/null
+++ b/Astrio/Task529/view/frontend/layout/collection_index_index.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
+    <head>
+        <title>Product list</title>
+    </head>
+    <body>
+        <referenceBlock name="content">
+            <block class="Astrio\Task529\Block\CustomBlock" name="astrio_product-collection" template="view.phtml">
+                <arguments>
+                    <argument name="jsLayout" xsi:type="array">
+                        <item name="components" xsi:type="array">
+                            <item name="product-list" xsi:type="array">
+                                <item name="component" xsi:type="string">Astrio_Task529/js/view/kocomponent</item>
+                                <item name="displayArea" xsi:type="string">product-list</item>
+                            </item>
+                        </item>
+                    </argument>
+                </arguments>
+            </block>
+        </referenceBlock>
+    </body>
+</page>
diff --git a/Astrio/Task529/view/frontend/templates/view.phtml b/Astrio/Task529/view/frontend/templates/view.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..7c03ebf1415554c914d5ba7552795c25f11c4c7f
--- /dev/null
+++ b/Astrio/Task529/view/frontend/templates/view.phtml
@@ -0,0 +1,26 @@
+<?php /** @var \Astrio\Task529\Block\CustomBlock $block */ ?>
+
+<div id="test-div" data-bind="scope:'product-list'">
+
+    <?php
+        $categories = $block->getCategories();
+    ?>
+    <span>Select a category</span>
+    <select id="selectCategory"
+            data-bind="event: { change: getProducts(document.getElementById('selectCategory').value) }"
+            name="categories">
+        <option value="">--Please choose an option--</option>
+        <?php foreach ($categories as $category) { ?>
+        <option value="<?php echo $category['value'] ?>"><?php echo $category['name'] ?></option>
+        <?php } ?>
+    </select>
+
+    <!-- ko template: getTemplate() --><!-- /ko -->
+    <script type="text/x-magento-init">
+      {
+          "#test-div": {
+              "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout(); ?>
+          }
+      }
+   </script>
+</div>
diff --git a/Astrio/Task529/view/frontend/web/js/view/kocomponent.js b/Astrio/Task529/view/frontend/web/js/view/kocomponent.js
new file mode 100644
index 0000000000000000000000000000000000000000..3d1ef1783a7bc6756734214a3b932ce8b8df7ba5
--- /dev/null
+++ b/Astrio/Task529/view/frontend/web/js/view/kocomponent.js
@@ -0,0 +1,38 @@
+define([
+    'ko',
+    'uiComponent',
+    'mage/url',
+    'mage/storage',
+], function (ko, Component, urlBuilder, storage) {
+    'use strict';
+    var id = 1;
+    return Component.extend({
+        defaults: {
+            template: 'Astrio_Task529/view',
+        },
+        products: ko.observableArray([]),
+        getProducts: function (categoryId = '', sortBy = '') {
+            var url = 'collection/ajax/products';
+            if (categoryId == '')
+                return;
+            else
+                url += '/categoryid/' + categoryId;
+            var self = this;
+            if (sortBy != '')
+                url += '/sortby/' + sortBy;
+            var serviceUrl = urlBuilder.build(url);
+            id++;
+            return storage.post(
+                serviceUrl, ''
+            ).done(function (response) {
+                self.products.removeAll();
+                response.forEach(function(element) {
+                    self.products.push(element);
+                    console.log(element);
+                });
+            }).fail(function (response) {
+                console.log(response);
+            });
+        },
+    });
+});
diff --git a/Astrio/Task529/view/frontend/web/template/view.html b/Astrio/Task529/view/frontend/web/template/view.html
new file mode 100644
index 0000000000000000000000000000000000000000..e02376fafad65eef0a2ea998829c551213ae3682
--- /dev/null
+++ b/Astrio/Task529/view/frontend/web/template/view.html
@@ -0,0 +1,29 @@
+<span>Sort by</span>
+<select id="sortBy" data-bind="event: { change: getProducts(document.getElementById('selectCategory').value,
+                                                            document.getElementById('sortBy').value)
+                                         }" name="sortBy">
+    <option value="">--Please choose an option--</option>
+    <option value="asc_price">By price in ascending order</option>
+    <option value="desc_price">By price in descending order</option>
+    <option value="asc_name">By name in ascending order</option>
+    <option value="desc_name">By name in descending order</option>
+</select>
+
+<table>
+    <thead>
+    <tr>
+        <th>Image</th>
+        <th>Name</th>
+        <th>Description</th>
+        <th>Price</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: products">
+    <tr>
+        <td><img data-bind="attr: {src: image}"></td>
+        <td data-bind="text: name"></td>
+        <td data-bind="html: description"></td>
+        <td data-bind="text: price"></td>
+    </tr>
+    </tbody>
+</table>