ViewModels (ArgumentInterface) vs. Classic Block Classes - Why ViewModels Are Preferred
ViewModels (ArgumentInterface) vs. Classic Block Classes - Why ViewModels Are Preferred
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Classic Magento often supplies data to templates through custom block classes that extend \Magento\Framework\View\Element\Template. In Hyvä projects - and generally as a modern Magento 2 convention - a ViewModel is preferred instead, implementing \Magento\Framework\View\Element\Block\ArgumentInterface.
The problem with custom block classes
A custom block class automatically inherits all of Template's complexity - caching logic, toHtml(), child block management, and much more that pure data supply doesn't need at all. On top of that, a template can only have one block as $block - if you need several independent data sources, you either nest further blocks or inject dependencies straight into the block class, which makes testing harder.
The ViewModel alternative
A ViewModel is a completely ordinary PHP class with constructor injection that implements ArgumentInterface - a pure marker interface with no required methods. You can have as many of them per template as you like, each with its own descriptive name in the Layout XML.
<?php
declare(strict_types=1);
namespace Mironsoft\TeamPage\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Supplies team members and categories for the team page.
*/
class TeamMembers implements ArgumentInterface
{
/**
* @param TeamMemberRepositoryInterface $teamMemberRepository Data source for team members.
*/
public function __construct(
private readonly TeamMemberRepositoryInterface $teamMemberRepository,
) {
}
/**
* Returns all active team members.
*
* @return array<int, array{name: string, role: string, category: string}>
*/
public function getTeamMembers(): array
{
return $this->teamMemberRepository->getActive();
}
}This class knows nothing about HTML rendering, caching, or child blocks - it just supplies plain data. That separation is exactly what makes ViewModels easy to test: a unit test instantiates the class with a mock repository, without having to drag along Magento's heavy block infrastructure at all.
Binding it in the template
In the template, the ViewModel is available under the argument name assigned in the Layout XML (see chapter 6):
<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Mironsoft\TeamPage\ViewModel\TeamMembers $teamViewModel */
$teamViewModel = $block->getData('team_view_model');
?>Multiple ViewModels per block
A block can bind several ViewModel arguments at once - for example one ViewModel for team data and a second for general store configuration. Each gets its own argument name="..." value in the Layout XML and is retrieved in the template under exactly that name.
Tipp: Rule of thumb for this project: new templates get a ViewModel, not a dedicated block class - unless there's a very concrete reason to override rendering behavior itself (which ViewModels fundamentally can't do, since they have no toHtml()).
When a block class is still appropriate
A custom block class still makes sense when rendering behavior genuinely needs to be overridden - custom caching, custom toHtml() logic, or managing many child blocks with complex ordering. For pure data supply, as in practically every case in this series, a ViewModel is enough.