16

I want to render a node's field inside a block. It works like this:

<?php

if ($node) {
  if (isset($node->field_body_secondary) && $field = $node->field_body_secondary->value) {
    $markup = render($field);
    $build = array(
      '#type' => 'markup',
      '#markup' => $markup,
    );
  }
}

return $build;

But this is not 100% as if I would just render the field normally, making it visible in the view mode settings.

5 Answers 5

2

I think the viewmode should be applied to the node, not to the field. So you need to get the viewbuilder and render the node. Afterwards you can pick the rendered array for the field from the node'r render array. Kind of like this:

$vb = [EntityTypeManager]->getViewBuilder('node'); // Drupal\node\NodeViewBuilder
$nodeview = $vb->view($node, $viewmode);
$fieldrenderarray = $nodeview[youfield-here];

P.S. You need to "[EntityTypeManager]" anyhow injected as @entity_type.manager service. Or get it in your Block-Plugin create() from $container->get('entity_type.manager').

7
  • 2
    not quite. $nodeview has #node as key
    – Alex
    Jul 22, 2016 at 10:26
  • 1
    also, this renders the node, not seperate fields
    – Alex
    Jul 22, 2016 at 10:32
  • 1
    Yes, I renders the node (and all it's fields). But this is what you adjust in your view mode settings. Maybe I got your question wrong. Jul 22, 2016 at 10:37
  • 1
    "$nodeview[youfield-here];" is null, at least on Drupal 8.6.x
    – Onkeltem
    Apr 16, 2019 at 4:06
  • 1
    Replace "yourfield-here" with the machine name of your field. Apr 16, 2019 at 14:42
50

To render a single field with the display setting of a view mode you can use the view() method of the field:

Example for rendering the image the way it's configured in the node's teaser view-mode:

$build['image'] = $node->field_image->view('teaser');

Or the body as it's configured in the node's full view-mode:

$build['body'] = $node->body->view('full');

Or if you want to have more fine-grained control, you can programmatically recreate how Drupal is doing view-modes:

$display_options = [
  'label' => 'hidden',
  'type' => 'entity_reference_entity_view',
  'settings' => [
    'view_mode' => 'some_media_image_view_mode',
  ],
];
$build['image'] = $node->field_image->view($display_options);

You can figure out the type/settings by inspecting the HTML on the view-modes form (e.g. /admin/structure/types/manage//display/full ) or export the configuration and inspect the YAML files.

7
  • 1
    unfortunately, this does not respect the view mode settings, like labels and formatter settings
    – Alex
    Jul 26, 2016 at 7:44
  • 1
    This should work, you can even use twig debug with this command. If you provide an existing view mode as string, the field will be formatted with this. If you provide an array you can give your own display settings, for example ['label' => 'inline' ]
    – 4uk4
    Jul 26, 2016 at 8:16
  • 1
    i believe i tried that already but it didnt work. i will try again. thanks so far
    – Alex
    Jul 26, 2016 at 8:18
  • 1
    I find it is respecting the formatter, but not the formatter settings. The output of this includes the correct settings.
    – Grayside
    May 1, 2017 at 20:05
  • 2
    this essentially got me to the answer, where I can get my variable in .twig: $vars['var_name'] = $node_object->field_name->view()[0]; and then in twig, I can render {{ var_name }}
    – bdanin
    Oct 5, 2017 at 23:04
8

This answer builds on https://drupal.stackexchange.com/a/208061/394

// Designate the field we want to render.
$field_name = 'body';
// Retrieve a render array for that field with the given view mode.
$render_array = $entity->$field_name->view('full');
// Render the result.
\Drupal::service('renderer')->renderRoot($render_array);

To completely programmatically render the field you finish by calling renderRoot(), which establishes a separate render context from what typical page responses would use -- a single render context for a request or sub-request. We could also use renderPlain(), but then it would escape all the things.

In the Drush repl but not in normal page execution, this threw a warning for me:

PHP warning:  DOMDocument::loadHTML(): Tag drupal-entity invalid in Entity, line: 1 in /drupal/core/lib/Drupal/Component/Utility/Html.php on line 286
5

Thanks to Rainer Feike's answer I came to the solution:

<?php

public function build() {
  $node = \Drupal::routeMatch()->getParameter('node');
  $build = array();
  $markup = array();

  $fieldsToRender = array(
    'field_node_ref', 'field_foo', 'field_bar',
  );

  $viewmode = 'default';
  $entityType = 'node';
  $display = entity_get_display($entityType, $node->getType(), $viewmode);
  $viewBuilder = \Drupal::entityTypeManager()->getViewBuilder($entityType);

  foreach ($fieldsToRender as $field_name) {
    if (isset($node->{$field_name}) && $field = $node->{$field_name}) {
      $fieldRenderable = $viewBuilder->viewField($field, $display->getComponent($field_name));
      if (count($fieldRenderable) &&! empty($fieldRenderable)) {
        $markup[] = \Drupal::service('renderer')->renderRoot($fieldRenderable);
      }
    }  
  }

  if (count($markup)) {
    $build = array(
      '#type' => 'markup',
      '#markup' => implode("", $markup),
    );
  }

  return $build;

}

Using $viewBuilder->viewField I can render any fields separately I need. I just need to find out how to add caching depending on the view mode settings, but this is another question :)

1
  • entity_get_display is deprecated as of 8.8.0 and removed in 9.0.0 Use \Drupal::service('entity_display.repository')->getViewDisplay as it has the same parameters. Dec 16, 2020 at 16:36
3

related to Alex's answer, here is how I modified it to use config_pages and build a global_footer block:

<?php

public function build() {
$config_name = 'global_footer';
$config = config_pages_config($config_name);
$build = array();
$markup = array();

$fieldsToRender = array(
  'field_body', 'field_foo', 'field_bar'
);

$viewmode = 'default';
$entityType = 'config_pages';
$display = entity_get_display($entityType, $config_name, $viewmode);
$viewBuilder = \Drupal::entityTypeManager()->getViewBuilder($entityType);

foreach ($fieldsToRender as $field_name) {
  if (isset($config->{$field_name}) && $field = $config->{$field_name}) {
    $fieldRenderable = $viewBuilder->viewField($field, $display->getComponent($field_name));
    if (count($fieldRenderable) &&! empty($fieldRenderable)) {
      $markup[] = \Drupal::service('renderer')->renderRoot($fieldRenderable);
    }
  }
}

if (count($markup)) {
  $build = array(
    '#type' => 'markup',
    '#markup' => implode("", $markup),
  );
}

return $build;

}

It's probably better to render arbitrary fields from a config_pages setup as opposed to pulling data from a node, but I guess it really depends on the specific use-case as to which method is best.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.