Blazor University Learn the .NET SPA framework from Microsoft
Ctrl X

Passing data to a RenderFragment

So far we've used RenderFragments that simply contains child mark-up, which is then included as-is when the component is rendered. As well as the standard RenderFragment class, there is also a generic RenderFragment<T> class that can be used to pass data into the RenderFragment.

Allowing the user to template tabs

Alter the TabControl component and add a new TabTextTemplate parameter property beneath the ChildContent parameter.

[Parameter]
public RenderFragment ChildContent { get; set; }

[Parameter]
public RenderFragment<TabPage> TabTextTemplate { get; set; }

Then change the mark-up in the foreach loop. What we need to do is check if TabTextTemplate has been set or not; if it hasn't then we render as usual, if it has been set then we execute the TabTextTemplate RenderFragment, passing in the TabPage from the foreach loop.

<CascadingValue Value="this">
  <div class="btn-group" role="group">
  @foreach (TabPage tabPage in Pages)
  {
    <button type="button"
      class="btn @GetButtonClass(tabPage)"
      @onclick=@( () => ActivatePage(tabPage) )>

    @if (TabTextTemplate != null)
    {
      @TabTextTemplate(tabPage)
    }
    else
    {
      @tabPage.Text
    }
  </button>
    }
  </div>
  @ChildContent
</CascadingValue>

To set the TabTextTemplate we need to edit the mark-up in the page where we are using the TabControl. This is done simply by adding a <TabTextTemplate> element inside the <TabControl> element, and everything inside that template will be considered the RenderFragment to use whenever the mark-up for a TabPage is rendered into a tab of the TabControl.

<TabControl>

  <TabTextTemplate>
    Hello
  </TabTextTemplate>

  <TabPage Text="Tab 1">
    <h1>The first tab</h1>
  </TabPage>
  <TabPage Text="Tab 2">
    <h1>The second tab</h1>
  </TabPage>
  <TabPage Text="Tab 3">
    <h1>The third tab</h1>
  </TabPage>
</TabControl>

But as soon as you do this, the compiler complains with the following error message.

Unrecognized child content inside component 'TabControl'. The component 'TabControl' accepts child content through the following top-level items: 'ChildContent', 'TabTextTemplate'.

When there is only a single RenderFragment parameter in your component, and it is named ChildContent, then Blazor will assume whenever we use the component and include content between the opening and closing tags we want to assign it to ChildContent. But once we have two RenderFragments in the consumer's mark-up, Blazor cannot assume that all of the content should be assigned to the ChildContent parameter. At this point the user of your component must explicitly create a <ChildContent> element to hold the content.

To make the intention clear, let's rename the ChildContent property to Tabs.

<TabControl>

  <TabTextTemplate>
    Hello
  </TabTextTemplate>

  <ChildContent>
    <TabPage Text="Tab 1">
      <h1>The first tab</h1>
    </TabPage>
    <TabPage Text="Tab 2">
      <h1>The second tab</h1>
    </TabPage>
    <TabPage Text="Tab 3">
      <h1>The third tab</h1>
    </TabPage>
  </ChildContent>
</TabControl>

Accessing context in the RenderFragment

So far the TabControl component will simply shows the text "Hello!" for the tab of each TabPage. What we need is access to the TabPage being rendered so we can output the value of its Text property. Note the use of TabTextTemplate in the TabControl component.

 @if (TabTextTemplate != null)
 {
  @TabTextTemplate(tabPage)
 }
 else
 {
   @tabPage.Text
 }

Within the foreach loop an HTML <button> is created, and within that button the preceding code is used to output the content that should appear for the user to click on. If TabTextTemplate is null then @tabPage.Text is rendered, but if TabTextTemplate is not null (the component user has specified a template) then the template is rendered, passing in the current tabPage of the loop for context.

When a Generic version of the RenderFragment<T> class is used, we must pass a value of <T> when rendering that fragment. The value passed to the fragment is available via a special variable named context. This can then be used to determine exactly what to render. In our case, we want to render the TabPage.Text property with some additional mark-up.

<TabTextTemplate>
  <img src="/images/tab.png"/> @context.Text
</TabTextTemplate>

Tabs templated by the component user

Avoiding @context name conflicts

If the name context conflicts with another identifier in your component it is possible to instruct Blazor to use a different name on a case-by-case basis by using the Context attribute on a RenderFragment.

For example, the TabTextTemplate mark-up demonstrated earlier could instead be written as follows.

<TabTextTemplate Context="TheTab">
  <img src="/images/tab.png"/> @TheTab.Text
</TabTextTemplate>