Content projection

✅ Similar to Blazor WebAssembly

Content projection in Blazor is the ability to create generic components that allow for the specification of one or more UI parts by the user at runtime. This means that you can design a component that handles common logic, while deferring specific logic to other components. Examples of where Content Projection is useful include a component that displays child components or HTML tags in a right-to-left format, or a tab container component that handles all the logic for tabs without knowledge of the specific tabs. In this tutorial, you will learn more about Content Projection and its various uses in Blazor:

  • What and when to use content projection?
  • Create a projectable component.
  • Generic projection.
You can download the example code used in this topic on GitHub.

What and when to use content projection?

Content projection is a technique that allows you to build a component that handles common logic and delegates specific logic to the component where it is used. For example, if you are building a website for an animal clinic, you may have different types of animals like dogs, cats, and turtles. Each animal has a web page to display information, and all animals share some common information such as name and age. However, some animals also have specific information such as skin cycle time or hibernation cycle. If you were to create a separate component for each species, you would end up with hundreds of components and would have to handle common information like name and age repeatedly. Content projection is a solution to this problem. It allows you to create a component that can display all common information of an animal and delegate specific information to another component. This technique is known as content projection.

why-use-content-projection.png


Create a projectable component

A RenderFragment in Blazor is a placeholder for projected UI. It allows components to accept and display content passed to them from outside. Assuming you have the following Tab component.

@code {
    [Parameter]
    public RenderFragment? Title { get; set; }
    
    [Parameter]
    public RenderFragment? Content { get; set; } = (__builder) =>
    {
        <div>Default content</div>
    };
}

In the example provided, the component Tab has two parameters defined - Title and Content - both of which are of type RenderFragment. The Content parameter also has a default value assigned to it, which will be displayed if no content is projected onto it. When this component is used, the projected content is placed within the Title and Content slots, as shown in the example.

<Tab>
    <Title>Blazor School</Title>
    <Content>Blazor Tutorial for .NET 7</Content>
</Tab>

Single projectable slot

When creating a projectable component that only requires a single slot, it can be beneficial to avoid naming the RenderFragment parameter with a specific name. This can lead to unnecessary redundancy in the component's usage. Instead, you can use ChildContent as a special name for the RenderFragment that catches all the content passed to it.

For example, in the code below, the component uses a RenderFragment parameter named Content which is not necessary.

@* Button Component *@

@code {
    [Parameter]
    public RenderFragment? Content { get; set; }
}
@* When it is used *@

<Button>
    <Content>Submit</Content>
</Button>

However, when you use ChildContent, it eliminates the need to specify the name of the RenderFragment when the component is used.

@* Button Component *@

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}
@* When it is used *@
<Button>Submit</Button>

This way, you can use the component simply by passing content within its opening and closing tags, making it more readable and less verbose.


Generic content projection

When working with dynamic and generic data, it can be beneficial to use generic content projection. This allows you to process and display the data in a more efficient and readable way. One of the main advantages of this approach is the ability to reduce redundant code. Take List<T> as an example. List<T> where T is a generic class, you can order the list, find items and more without writing any code.

For example, when displaying a list of animals, you might be tempted to use a foreach loop with multiple if-else statements to determine the type of animal and display the appropriate content. However, this approach can lead to a lot of repetitive code, making it difficult to read and maintain.

@foreach(var animal in Animals)
{
    @if(animal is Dog)
    {
        <div>My name is @animal.Name, I am a dog.</div>
    }

    @if(animal is Cat)
    {
        <div>My name is @animal.Name, I am a cat.</div>
    }
        
    @if(animal is Bird)
    {
        <div>My name is @animal.Name, I am a bird.</div>
    }
}

Instead, you can use a generic content projection component that allows you to specify different templates for different types of animals. This way, you only have to define the templates once and the component takes care of displaying the appropriate content based on the type of animal.

<AnimalDisplay InputData="Animals" Context="animal">
    <DogTemplate>
        <div>My name is @animal.Name, I am a dog.</div>
    </DogTemplate>
    <CatTemplate>
        <div>My name is @animal.Name, I am a cat.</div>
    </CatTemplate>
    <BirdTemplate>
        <div>My name is @animal.Name, I am a bird.</div>
    </BirdTemplate>
</AnimalDisplay>

This approach not only makes the code more readable, but it also allows you to perform additional logic processing on the data, such as sorting the list or updating property values, within the AnimalDisplay component. Overall, generic content projection is a powerful tool that can help you write more efficient and maintainable code.

Create a generic content projection component

  1. When creating a new generic content projection component, start by defining the type parameter T using the @typeparam directive. T can be restricted to an interface or a base class. For example:
@typeparam T where T : IAnimal
  1. Next, define placeholders for the projectable RenderFragment. If your component is processing data, declare input parameters and methods to process the data. For example:
@code {
    [Parameter]
    public T Animal { get; set; }

    [Parameter]
    public RenderFragment<string>? ChildContent { get; set; }

    private string GetExamineDetail(IAnimal animal) => $"The age is: {Animal.Age}.";
}

Then you can use the generic content projection component as follows:

@foreach (var animal in Animals)
{
    <AnimalDetail Animal="animal" />
}

@code {
    public List<IAnimal> Animals { get; set; } = new()
    {
        new Dog() { Name = "Doggy", Age = 2 },
        new Bird() { Name = "Birb", Age = 0 },
        new Cat() { Name = "Kitty", Age = 2 }
    };
}

Or access some processed data:

<AnimalDetail Animal="Stray" Context="examinateDetail">
    We have found this stray cat in a bush. @examinateDetail
</AnimalDetail>

@code {
    public Cat Stray { get; set; } = new()
    {
        Name = "Stray Cat",
        Age = 3
    };
}

In this example, the examinateDetail is the result of the method GetExamineDetail we have defined before.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2024 Blazor School
An unhandled error has occurred. Reload 🗙