Taking the Block out of Blocks with View Models

Are you wondering how to add custom functionality to blocks in Magento 2? In the third part of our Magento 2 Development series, Yoda – one of our Magento wizards explains how to do this with view models and answers questions you might have about them.

The Need For View Models

Working with blocks in Magento 2 is a tricky thing at times. A lot of Magento 1 developers have the instinctual reaction to extend the Magento block class in order to add the extra functionality they need, and they’re not entirely wrong to think so, but there are some flaws with this idea.

The Magento\Framework\View\Element\Template class is the default class for displaying PHTML templates, but the PHPdoc for the class has a line that explicitly says “Avoid extending this class”. It also says “If you need custom presentation logic in your blocks, use this class as block, and declare custom view models in block arguments in layout handle file” along with an example of how to do this, however it doesn’t really cover how view models should be set up.

Magento’s example:
<block name=”my.block” class=”Magento\Backend\Block\Template” template=”My_Module::template.phtml” >
<arguments>
<argument name=”viewModel” xsi:type=”object”>My\Module\ViewModel\Custom</argument>
</arguments>
</block>

Now, if you’re not supposed to extend the Template class, then surely extending the Magento\Framework\View\Element\AbstractBlock class is okay? Not so: “Avoid inheriting from this class. Will be deprecated.”

If we can’t extend the Template class, and we can’t extend AbstractBlock, then we should probably learn how to use view models, shouldn’t we?

Luckily, Vinai Kopp wrote an article about this and we can build on that here.

What’s a View Model?

A view model is a class which you pass to a template in order to provide advanced functionality to a template. In an ideal world, template files should contain no functionality, and should only be displaying the output provided to them by other objects. In order to facilitate this, we can pass view models where the calculations, object fetches, data lookups etc are done, so that the template can be kept as simple as possible.

The only requirement is that the class must implement Magento\Framework\View\Element\Block\ArgumentInterface. If it doesn’t implement ArgumentInterface, it will not be able to be passed to a template file. Beyond that, the world is your oyster!

How Do I Create a View Model?

My preference is that view models should be placed in the block directory, as they exist to facilitate the frontend of the website, but they can go in any directory. They can be models, helpers, or entirely custom classes as you prefer. I would recommend that they should be custom classes which utilise constructor injection to fetch the relevant classes they need.

The essential base structure is something like this:

<?php
namespace DigitalSix\Example\Block;

class ViewModel implements \Magento\Framework\View\Element\Block\ArgumentInterface {
public function __construct(

) {

}
}

Once you’ve created your class, you can then inject it into your template by editing the pertinent layout XML file like so:

<referenceContainer name=”columns.top”>
<block name=”view-model-example” template=”DigitalSix_Example::example.phtml”>
<arguments>
<argument name=”view_model” xsi:type=”object”>DigitalSix\Example\Block\ViewModel</argument>
</arguments>
</block>
</referenceContainer>

How Do I Get The View Model in my Template?

Once you’ve created your view model, and passed it to your template, you’ll need to fetch it in the template. As it has been passed as an argument to the template, you can fetch it using the getData() function on the $block object within the template using the argument name you specified in the layout XML file.

<?php
$viewModel = $block->getData(“view_model”);

What Do I Use It For?

Anything you would normally think to extend a block to do, you can create a view model for instead. Or if you find you need a particular object inside a template for some reason, the best way to get it is to create a view model which fetches it via dependency injection so you can then use a function in the view model to fetch the object. An example of how it could be used:

<?php

namespace DigitalSix\Example\Block;

class ViewModel implements \Magento\Framework\View\Element\Block\ArgumentInterface {
/** @var \Magento\Framework\App\Config\ScopeConfigInterface */
private $_scopeConfig;

public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
) {
$this->_scopeConfig = $scopeConfig;
}

public function isFeatureEnabled() {
return $this->_scopeConfig->getValue(“digitalsix/example/enabled”);
}
}

And then in the template file:

<?php
$viewModel = $block->getData(“view_model”);
if (false == $viewModel->isFeatureEnabled()) {
return; // not enabled, don’t use template
}

Summary

View models are the best way to add custom functionality to blocks. Rather than extending the Magento Template or AbstractBlock classes, we should be creating custom view models and injecting them into the template via layout XML. This reduces incompatibility issues and improves long-term code health.

Look out for the next article of our Magento 2 Development series to find out what exactly Magento DI does and how to use dependency injection.

Leave a Reply

Your email address will not be published. Required fields are marked *

seventeen − two =

GET IN TOUCH
Digital Six
Digital Six Ltd, Norloch House, 36 King's Stables Road, Edinburgh,
EH1 2EU
Partnerships