Arbitrary attributes and parameters in Blazor


In this article we will discuss how a component can accept arbitrary attributes. This is continuation to our previous article Attribute Splatting.

Child Component (ChildComponent.razor)

Dictionary parameter InputAttributes is applied to the <input> element using @attributes razor directive. This InputAttributes parameter allows a parent component to be able to pass arbitrary attributes.

<input id="firstName" @attributes="InputAttributes" />

@code {
    [Parameter]
    public Dictionary<string, object> InputAttributes { get; set; } = 
        new Dictionary<string, object>() 
        {
            { "placeholder", "Child Component Placeholder" }
        };
}

Parent Component (ParentComponent.razor)

The parent component is passing 2 attributes (required and placeholder) to the child component using the dictionary parameter InputAttributes. Using this technique, you can pass any number of arbitrary attributes from the parent component to child component.

<ChildComponent InputAttributes="attributesFromParent">
</ChildComponent>

@code {
    public Dictionary<string, object> attributesFromParent { get; set; } = 
        new Dictionary<string, object>() 
        {
            { "required", "required" },
            { "placeholder", "Parent Component Placeholder" }
        };
}

However, what you cannot do is specify the arbitrary attributes in the HTML. In this example maxlength attribute is included in the HTML. This causes InvalidOperationException: Object of type 'EmployeeManagement.Web.Pages.ChildComponent' does not have a property matching the name 'maxlength'. One way to fix this is to include a corresponding parameter in the child component. It's tedious to include a parameter for every attribute especially if we want to support large number of attributes.

<ChildComponent InputAttributes="attributesFromParent" maxlength="15">
</ChildComponent>

@code {
    public Dictionary<string, object> attributesFromParent { get; set; } = 
        new Dictionary<string, object>() 
        {
            { "required", "required" },
            { "placeholder", "Parent Component Placeholder" }
        };
}

CaptureUnmatchedValues Property

To be able to capture attribute values that do not have matching parameters, simply set CaptureUnmatchedValues property of the dictionary parameter to true.

Child Component (ChildComponent.razor)

<input id="firstName" @attributes="InputAttributes" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } = 
        new Dictionary<string, object>() 
        {
            { "placeholder", "Child Component Placeholder" }
        };
}

Parent Component (ParentComponent.razor)

The parent component is passing 3 arbitrary attributes - maxlength, placeholder and required.

<ChildComponent maxlength="15" placeholder="Parent Component Placeholder" required>
</ChildComponent>

When CaptureUnmatchedValues property is set to true, we cannot explicitly set the value for the dictionary property in the parent component. If we try to do that, we get the following exception.

InvalidOperationException: The property 'InputAttributes' on component type 'EmployeeManagement.Web.Pages.ChildComponent' cannot be set explicitly when also used to capture unmatched values.

Default attribute values

If required, the child component can specify default values. However, the order in which the default values and @attributes directive are applied matters.

Child Component (ChildComponent.razor)

In this example, a default value for the placeholder attribute is specified by the child component. This default value is present on the left hand side of the @attributes directive. 

<input id="firstName" placeholder="Default Placeholder" @attributes="InputAttributes" />

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } =
        new Dictionary<string, object>();
}

Parent Component (ParentComponent.razor)

Parent component did not explicitly pass a value for the placeholder attribute, so the child component default placeholder value will be used.

<ChildComponent></ChildComponent>

In the example below, parent component is passing a value for the placeholder attribute, so this will override the child component default placeholder value as expected.

<ChildComponent placeholder="Parent Component Placeholder">
</ChildComponent>

Now, in the child component, reverse the @attributes directive and the placeholder default value.

Child Component (ChildComponent.razor)

The attributes are always processed from right to left, so the default placeholder attribute value specified in the child component is always used, even if the parent component passes an explicit value for the placeholder attribute.

<input id="firstName" @attributes="InputAttributes" placeholder="Default Placeholder"/>

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } =
        new Dictionary<string, object>();
}

Parent Component (ParentComponent.razor)

Parent Component has specified an explicit value for the placeholder attribute, but this will be overriden by the child component.

<ChildComponent placeholder="Parent Component Placeholder">
</ChildComponent>




© 2020 Pragimtech. All Rights Reserved.