How to Create a Custom Block that Embeds Nodes in Drupal 8

This is a journey of epic proportions, not suited for the faint of heart. The other day we were looking for a way to embed a certain entity type and set the view mode, well a node, but it could've been any other entity type.

Now this may seem like a fairly easy task at first, but you will need to reach into your pocket of Drupal 8 skills to solve this one. You will need to know about Plugins, Dependency Injection, Interfaces, you name it.

Example of a node embed field

So the journey begins with creating a php file for the block plugin. Easy, right? Open up your IDE, find your custom module (let's call this one project Foo module Bar) and create the file for the block (let's name it Node Embed Block) so /foo/modules/custom/bar/src/Plugin/Block/NodeEmbedBlock.php and then the fun starts.

As you may already know, the Block Plugin extends the BlockBase class.  You'll need your basic methods: build(), blockForm(), blockValidate() [optional] and your blockSubmit().

Assuming you already know how to create a Block plugin and how to put the respective pieces into each function (won't bore you to death with the details) let's skip to the good parts.

Entity Autocomplete: Sure having a text field that takes the NID of the node you want to render sounds effective enough. Much simpler than creating an AJAX callback to do some sort of fancy autocomplete, right? Wrong! All that cool work has already been done for you and all you have to do to leverage it is call this new field type: entity_autocomplete check it out on the blockForm() function in the code below.

Dependency Injection: For the view modes, there's a class that has all that I want (\\Drupal\\Core\\Entity\\EntityDisplayRepositoryInterface), but it's not part of my block class. Solution? Dependency injection.

For this we will have to tweak the module a little bit. First we need to declare which classes we're using:

use Drupal\\Core\\Plugin\\ContainerFactoryPluginInterface;
use Symfony\\Component\\DependencyInjection\\ContainerInterface;

Now we're ready to extend the block plugin a little bit more by telling PHP that our class implements the ContainerFactoryPluginInterface. And that means that we need to add two more methods to our class: public function \_\_construct() to assign the class that we want to use  $this\->entityDisplayRepository \= $entity\_display\_repository; and public static function create() to create the container and inject our dependency: $container\->get('entity\_display.repository')

Again, see the code below for the implementation details, for instance in the \_\_construct() function you will need to specify the type of the class you're expecting.

But now that we have all the pieces fitting together, to get the view modes we can now call something like this:

$view_modes = $this->entityDisplayRepository->getAllViewModes();

And now the code:

<?php

namespace Drupal\bar\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\node\Entity\Node;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @file
 * Render a node inside a block.
 */

/**
 * Provides a 'Node Embed' block.
 *
 * @Block(
 *   id = "node_embed_block",
 *   admin_label = @Translation("Node Embed"),
 *   category = @Translation("Content")
 * )
 */
class NodeEmbedBlock extends BlockBase implements ContainerFactoryPluginInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
   */
  protected $entityDisplayRepository;

  /**
   * Constructs a NodeEmbedBlock instance.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityDisplayRepositoryInterface $entity_display_repository) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityDisplayRepository = $entity_display_repository;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_display.repository')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $block['content'] = t('Content not found');

    $config = $this->getConfiguration();

    if (isset($config['nid'])) {
      /* @var $node \Drupal\node\NodeInterface */
      $node = Node::load($config['nid']);
      $view_mode = (isset($config['view_mode']) && !empty($config['view_mode']) ? $config['view_mode'] : 'full');
      $block['content'] = node_view($node, $view_mode);
    }

    return $block;
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {

    $form = parent::blockForm($form, $form_state);

    // Retrieve existing configuration for this block.
    $config = $this->getConfiguration();

    // Add a form field to the existing block configuration form.
    $form['nid'] = array(
      '#title' => t('Node'),
      '#description' => t('The node you want to display'),
      '#type' => 'entity_autocomplete',
      '#target_type' => 'node',
      '#selection_handler' => 'default',
      '#default_value' => ((isset($config['nid']) && !empty($config['nid'])) ? Node::load($config['nid']) : NULL),
      '#required' => TRUE,
    );

    // View modes.
    $options = [];
    $view_modes = $this->entityDisplayRepository->getAllViewModes();
    if (isset($view_modes['node'])) {
      foreach ($view_modes['node'] as $view_mode => $view_mode_info) {
        $options[$view_mode] = $view_mode_info['label'];
      }
    }

    $form['view_mode'] = array(
      '#title' => t('View mode'),
      '#description' => t('Select the view mode you want your node to render in.'),
      '#type' => 'select',
      '#options' => $options,
      '#default_value' => (isset($config['view_mode']) && !empty($config['view_mode']) ? $config['view_mode'] : 'full'),
      '#required' => TRUE,
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    // Save our custom settings when the form is submitted.
    $this->setConfigurationValue('nid', $form_state->getValue('nid'));
    $this->setConfigurationValue('view_mode', $form_state->getValue('view_mode'));
  }

}