---
sidebar_position: 2
title: Composant générique
---

Les composants génériques sont des composants qui acceptent un ou plusieurs modèles d’interface utilisateur en tant que paramètres, qui peuvent ensuite être utilisés dans le cadre de la logique de rendu du composant. 
Les composants génériques vous permettent de créer des composants de niveau supérieur qui sont plus réutilisables que les composants standard. Voici quelques exemples :
* Composant de table qui permet à un utilisateur de spécifier des modèles pour l’en-tête, les lignes et le pied de page de la table.
* Composant de liste qui permet à un utilisateur de spécifier un modèle pour le rendu d’éléments dans une liste.

## Création du composant

Créer un nouveau composant qui servira de base au composant générique :

```cshtml title="Components/Card.razor"
<h3>Card</h3>
<div class="card text-center">
    @CardHeader
    @CardBody
    @CardFooter
</div>
```

```csharp title="Components/Card.razor.cs"
public partial class Card
{
	[Parameter]
	public RenderFragment CardBody { get; set; }

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

	[Parameter]
	public RenderFragment CardHeader { get; set; }
}
```

`@CardHeader`, `@CardBody` et `@CardFooter` sont des composants Templates en paramètre de type `RenderFragment`.

```cshtml title="Pages/Index.razor"
...

<Card>
    <CardHeader>
        <div class="card-header">
           My Templated Component
        </div>
    </CardHeader>
    <CardBody>
        <div class="card-body">
            <h5>Welcome To Template Component</h5>
        </div>
    </CardBody>
    <CardFooter>
        <div class="card-footer text-muted">
            Click Here
        </div>
    </CardFooter>
</Card>

...
```

Chaque paramètre de Template de type `RenderFragment` aura une balise Html correspondant. La balise HTML doit correspondre au nom du paramètre.

**Rendu:**

![Render](/img/blazor-component/RenderFragmentCard.png)

## Duplication de balise Html

La balise Html qui représente le paramètre du composant Template, la dupliquer entraînera le rendu de la dernière balise Html ou celle du bas en priorité.

```cshtml title="Pages/Index.razor"
...

<Card>
    <CardHeader>
        <div class="card-header">
            Templated Component
        </div>
    </CardHeader>
    <CardHeader>
        <div class="card-header">
            Hi I'm duplicated header
        </div>
    </CardHeader>
</Card>

...
```

**Rendu:**

![Render](/img/blazor-component/duplicate.png)


## Parameter typé `RenderFragment<T>`

* `RenderFragmetnt<T>` est un paramètre typé qui réprésente une partie du rendu UI avec du contenu dynamique du type qu'il implémente.
* Déclarez le paramètre de type générique en haut du composant en utilisant `@typeparam`. Cela représente le type de composant au moment de l'exécution.
* Ce type de paramètre de composant typé qui est passé comme élément aura un paramètre implicite appelé `context`. 
* Le paramètre implicite de contexte aide à la liaison dynamique des données. Il a accès au type de propriété implémenté par `RenderFragment<T>`.

```cshtml title="Components/Card.razor"
@typeparam TItem
<div class="card text-center">
    @CardHeader(Item)
    @CardBody(Item)
    @CardFooter
</div>
```

```csharp title="Components/Card.razor.cs"
public partial class Card<TItem>
{
	[Parameter]
	public RenderFragment<TItem> CardBody { get; set; }

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

	[Parameter]
	public RenderFragment<TItem> CardHeader { get; set; }

	[Parameter]
	public TItem Item { get; set; }
}
```

`CardHeader`, `CardBody` sont des paramètres de type `RenderFragment<T>`. Ces propriétés modélisées Html ont accès au paramètre implicite `context` pour les données de liaison dynamiques.

```csharp title="Models/Cake.cs"
public class Cake
{
	public int Id { get; set; }
	public string Name { get; set; }
	public decimal Cost { get; set; }
}
```

```cshtml title="Pages/Index.razor"
...

<Card Item="CakeItem">
    <CardHeader>
        <div class="card-header">
            Cake Token Number - @context.Id
        </div>
    </CardHeader>
    <CardBody>
        <div class="card-body">
            <div>@context.Name</div>
            <div>$ @context.Cost</div>
        </div>
    </CardBody>
    <CardFooter>
        <div class="card-footer text-muted">
            Click Here
        </div>
    </CardFooter>
</Card>

...
```

```csharp title="Pages/Index.razor.cs"
public partial class Index
{
	private Cake CakeItem = new Cake
	{
		Id = 1,
		Name = "Black Forest",
		Cost = 50
	};
}
```

**Rendu:**

![Render](/img/blazor-component/typeRenderFragment.png)


## Attribut de contexte sur l'élément Template HTML

Dans le composant, le contexte peut être déclaré explicitement en tant qu'attribut Html. 
La valeur affectée au contexte (attribut Html) sera utilisée pour la liaison dynamique dans tous les templates du composant.

Déclaration de l'attribut Context au niveau du composant.

```cshtml title="Pages/Index.razor"
...

<Card Item="CakeItem" Context="cakeContext">
    <CardHeader>
        <div class="card-header">
            Cake Token Number - @cakeContext.Id
        </div>
    </CardHeader>
    <CardBody>
        <div class="card-body">
            <div>@cakeContext.Name</div>
            <div>$ @cakeContext.Cost</div>
        </div>
    </CardBody>
</Card>

...
```

Attribut de contexte de déclaration au niveau de chaque template.

```cshtml title="Pages/Index.razor"
...

<Card Item="CakeItem">
    <CardHeader Context="headContext">
        <div class="card-header">
            Cake Token Number - @headContext.Id
        </div>
    </CardHeader>
    <CardBody Context="bodyContext">
        <div class="card-body">
            <div>@bodyContext.Name</div>
            <div>$ @bodyContext.Cost</div>
        </div>
    </CardBody>
</Card>

...
```

## Composant générique

Le composant template est lui même un composant générique. `@typeparam` définie le type du composant générique. 
Le composant template générique sera défini par notre propre implémentation et il peut être réutilisé pour différents types.

Le meilleur choix de son implémentation est comme le composant avec seulement deux propriétés. 
La première propriété est de type `RenderFragment<T>` pour restituer le contenu. 
La deuxième propriété est de type `List<T>` une collection de données à lier au rendu.

Les scénarios les plus largement utilisés pour le composant générique sont des éléments d'affichage, 
cela aidera à éviter de créer de nouveaux composants pour afficher des éléments dans une application. 
Un composant unique pour tous les éléments d'affichage.

```cshtml title="Components/ShowItems.razor"
@typeparam TItem

<div>
    @if ((Items?.Count ?? 0) != 0)
    {
        @foreach (var item in Items)
        {
            @ShowTemplate(item);
        }
    }
</div>
```

```cshtml title="Components/ShowItems.razor.cs"
public partial class ShowItems<TItem>
{
	[Parameter]
	public List<TItem> Items { get; set; }

	[Parameter]
	public RenderFragment<TItem> ShowTemplate { get; set; }
}
```

`ShowTemplate` et `Items` sont deux paramètres de notre composant de type générique. 
`ShowTemplate` est une propriété de type `RenderFragment<TItem>` qui rendra le Html à afficher (ce Html peut être un tableau, une liste, un contenu, etc.). 
`Items` est une collection dont le type correspond à notre type de composant, ces données seront utilisées dans la liaison de données à l'intérieur de notre Html rendu.

```cshtml title="Pages/Index.razor"
...

<ShowItems Items="Cakes" >
    <ShowTemplate Context="CakeContext">
        <div class="card text-center">
            <div class="card-header">
                Cake Token Id - @CakeContext.Id
            </div>
            <div class="card-body">
                <h5 class="card-title">@CakeContext.Name</h5>
                <p class="card-text">Price $@CakeContext.Cost</p>
            </div>
            <div class="card-footer text-muted">
                Click Here
            </div>
        </div>
    </ShowTemplate>
</ShowItems>

...
```

Le template `ShowItems` représente notre composant de type générique. `ShowTemplate` représente la propriété de rendu HTML du composant.

```csharp title="Pages/Index.razor.cs"
public partial class Index
{
	...
	
	public List<Cake> Cakes { get; set; }

	protected override Task OnAfterRenderAsync(bool firstRender)
	{
		LoadCakes();
		StateHasChanged();
		return base.OnAfterRenderAsync(firstRender);
	}

	public void LoadCakes()
	{
		Cakes = new List<Cake>
		{
			// items hidden for display purpose
			new Cake
			{
				Id = 1,
				Name = "Red Velvet",
				Cost = 60
			},
		};
	}
	
	...
}
```

À l'intérieur de la méthode `OnAfterRenderAsync` on appele la méthode `StageHasChanged()` pour refléter les modifications de données dans le composant. 
Vous pouvez essayer de supprimer la méthode `StateHasChanged()` et vous pouvez observer une page vide sans afficher de données car nous remplissons des données avant de rendre Html.

**Rendu:**

![Render](/img/blazor-component/genericStatic.png)



## Notion : RenderFragment

Les composants template sont des composants qui acceptent un ou plusieurs modèles d’interface utilisateur en tant que paramètres, qui peuvent ensuite être utilisés dans le cadre de la logique de rendu du composant. 
Les composants template vous permettent de créer des composants de niveau supérieur qui sont plus réutilisables que les composants normaux. 

Voici quelques exemples :
* Composant de table qui permet à un utilisateur de spécifier des template pour l’en-tête, les lignes et le pied de page de la table.
* Composant de liste qui permet à un utilisateur de spécifier un template pour afficher des éléments dans une liste.

Un composant template est défini en spécifiant un ou plusieurs paramètres de composant de type `RenderFragment` ou `RenderFragment<TValue>`. 
Un fragment de rendu représente un segment de l’interface utilisateur à restituer. `RenderFragment<TValue>` prend un paramètre de type qui peut être spécifié lors de l’appel du fragment de rendu.

Souvent, les composants template sont typés de façon générique, comme le montre le composant suivant `TableTemplate`. 
Le type générique `<T>` dans cet exemple est utilisé pour restituer des valeurs `IReadOnlyList<T>`, qui dans ce cas est une série de lignes PET dans un composant qui affiche une table d’animaux familiers.

```cshtml title="Shared/TableTemplate.razor"
@typeparam TItem
@using System.Diagnostics.CodeAnalysis

<table class="table">
    <thead>
        <tr>@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Items)
        {
            if (RowTemplate is not null)
            {
                <tr>@RowTemplate(item)</tr>
            }
        }
    </tbody>
</table>

@code {
    [Parameter]
    public RenderFragment? TableHeader { get; set; }

    [Parameter]
    public RenderFragment<TItem>? RowTemplate { get; set; }

    [Parameter, AllowNull]
    public IReadOnlyList<TItem> Items { get; set; }
}
```

Lorsque vous utilisez un composant template, les paramètres de modèle peuvent être spécifiés à l’aide d’éléments enfants qui correspondent aux noms des paramètres. 
Dans l’exemple suivant, `<TableHeader>...</TableHeader>` et `<RowTemplate>...<RowTemplate>` fournissent des template `RenderFragment<TValue>` pour `TableHeader` et `RowTemplate` du composant `TableTemplate`.

Spécifiez l'attribut `Context` sur l’élément composant lorsque vous souhaitez spécifier le nom du paramètre de contenu pour le contenu enfant implicite (sans élément enfant d’encapsulation). 
Dans l’exemple suivant, l'attribut `Context` apparaît sur l'élément `TableTemplate` et s’applique à tous les paramètres de template `RenderFragment<TValue>`.

```cshtml title="Pages/Pets1.razor"
@page "/pets1"

<h1>Pets</h1>

<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}
```

Vous pouvez également modifier le nom du paramètre à l’aide de l’attribut `Context` sur l'élément enfant `RenderFragment<TValue>`. 
Dans l’exemple suivant, le `Context` est défini sur `RowTemplate` au lieu de `TableTemplate` :

```cshtml title="Pages/Pets2.razor"
@page "/pets2"

<h1>Pets</h1>

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}
```

Les arguments de composant de type `RenderFragment<TValue>` ont un paramètre implicite nommé `context`, qui peut être utilisé. 
Dans l’exemple suivant, `Context` n’est pas défini. `@context.{PROPERTY}` fournit des valeurs de `PET` au modèle, où `{PROPERTY}` est une propriété `Pet` :

```cshtml title="Pages/Pets3.razor"
@page "/pets3"

<h1>Pets</h1>

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}
```

Lorsque vous utilisez des composants génériques, le paramètre de type est déduit si possible. 
Toutefois, vous pouvez spécifier explicitement le type avec un attribut dont le nom correspond au paramètre de type, qui est `TItem` dans l’exemple précédent :

```cshtml title="Pages/Pets4.razor"
@page "/pets4"

<h1>Pets</h1>

<TableTemplate Items="pets" TItem="Pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string? Name { get; set; }
    }
}
```

Par défaut un nouveau composant blazor n'accepte pas contenu si vous ne déclarer pas un `RenderFragment` nommé `ChildContent`.

```cshtml title="Components/TestRenderFragment.razor"
<h3>TestRenderFragment</h3>

@code {

}
```

```cshtml title="Pages/Index.razor"
...

<TestRenderFragment>
    <div>Content of my TestRenderFragment</div>
</TestRenderFragment>

...
```

Ce code ne génére pas d'erreur de compilation mais déclenchera l'erreur suivante lors de l'éxécution :

```
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Object of type 'MyBeautifulAdmin.Components.TestRenderFragment' does not have a property matching the name 'ChildContent'.
System.InvalidOperationException: Object of type 'MyBeautifulAdmin.Components.TestRenderFragment' does not have a property matching the name 'ChildContent'.
```

Afin de rendre le code enfant, utiliser le code suivant :

```cshtml title="Components/TestRenderFragment.razor"
<h3>TestRenderFragment</h3>

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

@ChildContent
```
