How to use Magento 2 ViewModel like a Pro

Likes (3)   Dislikes (0)  

Have you asked yourself a question like me? I always keep on asking a generic question to myself while developing in Magento 2. The question is where should I place a particular piece of code that I am going to just compose. Where should the piece of code go, a model or a block or a helper or something else? Asking yourself this question and thinking on it let you make the right decision and the code finally get the proper place.

Today I will explain about the old Magento Block class and how we used to keep data retrieval code inside it just for the sake of simplicity. Any block of code that is aimed at data reading should go inside models. But old Magento was using blocks for the same to provide data to block template files. With Magento 2 and use of PSR culture Magento has grown a lot and that feels like natural programming.

There exists 3 simple and magic steps to integrate ViewModel in your new Magento 2 module.

  1. Configure or link the ViewModel class to your Block class
  2. Declare the ViewModel class itself
  3. Use the ViewModel instance inside your template file

What is a ViewModel in Magento 2?

You can say a ViewModel is a model for your view or the phtml file. That means instead of putting all data reading functionality inside of a block class we can move those inside a ViewModel. This is just a thing of personal preference how you like to manage your code and all that. Magento 2 is both MVC and MVVM. I have seen developers using helper classes to read data from within a template file. That is all old school style of coding standard. This is entirely discouraged in Magento 2 starting from v2.2. The gist of this discussion is that we need to somehow extract our data reading code to separate place which can be maintained easily and can be reused. And Magento’s ViewModel is the best solution for this.

Role of a ViewModel in Magento 2

A view is meant for the display or the representation purpose. It has nothing to do with data mutation (means changing the original data). If we think in this point of view a ViewModel is the source of data for the view. Hence, ViewModel should only read the data and do not mutate it. That’s why we should not write any data mutation logic in a ViewModel. Our ViewModel should not directly or indirectly change data inside a database. Only reading of the existing data should be the main purpose of a ViewModel.

Configuring a ViewModel in Magento 2 Layout

For your phtml file to be able to use a ViewMode it has to be configured in the layout. Magento uses the Dependency Injection technique here to achieve this.

layout xml file
<block class="FQN of your Block class" name="name.of.your.block" template="path_to_your_template_file">
    <arguments>
        <argument name="view_model" xsi:type="object">FQN of your ViewModel class</argument>
    </arguments>
</block>

Whatever we pass here inside arguments are injected into the block class by DI container. And we can read the same as a property of the block class on the template file.

Defining the new ViewModel Class

As per Magento 2 all ViewModel classes goes inside the company/module/ViewModel/ directory thanks to PSR standards. The public functions in this class are accessible inside your template file.

PHP
<?php

declare(strict_types=1);

namespace Training\CookieConsent\ViewModel;

use Training\CookieConsent\Model\ResourceModel\Cookie\Collection as CookieCollection;
use Magento\Framework\View\Element\Block\ArgumentInterface;

/**
 * A view model to provide all cookies and their respective categories
 */
class CookieManager implements ArgumentInterface
{
    private CookieCollection $cookieCollection;
    
    const ESSENTIAL_COOKIE_CATEGORY = 'Essential';

    public function __construct(
        CookieCollection $cookieCollection
    )
    {
        $this->cookieCollection = $cookieCollection;
    }

    /**
     * get all cookies and their respective categories
     * @return array
     */
    public function getAllCookies(): array
    {
        $cookies = $this->collection->join(
            ['category' => 'cookie_consent_categories'],
            'main_table.category_id = category.id',
            ['category_name' => 'category.name', 'category_description' => 'category.description']
        )->getItems();

        $categoryWiseCookies = [];
        foreach($cookies as $cookie) {
            if (!isset($categoryWiseCookies[$cookie['category_id']])) {
                $categoryWiseCookies[$cookie['category_id']] = [];
                $categoryWiseCookies[$cookie['category_id']]['cookies'] = [];
                $categoryWiseCookies[$cookie['category_id']]['category'] = [
                    'id'             => $cookie['category_id'],
                    'name'           => $cookie['category_name'],
                    'description'    => $cookie['category_description'],
                    'is_essential'   => str_contains($cookie['category_name'], self::ESSENTIAL_COOKIE_CATEGORY )
                ];
            }
            $categoryWiseCookies[$cookie['category_id']]['cookies'][] = $cookie->getData();
        }
        return $categoryWiseCookies;
    }
}

All ViewModel classes are required to implement the Magento\Framework\View\Element\Block\ArgumentInterface to be able to be inserted into the arguments section of the Block class in the layout file as we have seen above. See the documentation of the ArgumentInterface below given by Magento 2.

/**
 * Block argument interface.
 * All objects that are injected to block arguments should implement this interface.
 *
 * @api
 * @since 101.0.0
 */
interface ArgumentInterface
{
}

Using the configured ViewModel in the Template

The configured ViewModel class will be instantiated by DI container and injected into the block class. Inside the block’s template file we can access the instance of the ViewModel like below.

PHP
$myCookiesManager = $block->getViewModel();

// $myCookiesManager = $this->getViewModel();
// $myCookiesManager = $block->getData('view_model');
// $myCookiesManager = $this->getData('view_model');

$cookies = $myCookiesManager->getAllCookies();

See how we are only using ViewModel to read some data from the database and we are not using it to change data. After getting the required data from the ViewModel we can use it any way we like inside the phtml template file.

Conclusion

ViewModels are great if we see in terms of SEPARATION OF CONCERN.

Bonus topic: The other way of configuring/linking/initializing ViewModel in a different situation

Read the official Magento 2 ViewModel documentation here.

Feel free to ask question if you have any or comment below how you like this topic.

Leave a Comment

Share via
Copy link
Powered by Social Snap