The Practical Client

How to Integrate Blazor Components

You can create Blazor components by combining other Blazor components but you'll almost certainly need to share data between those components. Here are all the options currently open to you.

If you've got a Blazor component called SalesOrderComponent you can integrate it into a Razor Page or View with this code:

<so>
 @(await Html.RenderComponentAsync<SalesOrderComponent>() )
</so>

If you want to pass data to that SalesOrderComponent, you just create an anonymous object, give it a property, set the property to whatever it is you want to pass to SalesOrderComponent, and pass that anonymous object to the RenderComponentAsync method. In this example, I've named the anonymous object's property sOrder and set it to whatever is in the Model property of my View:

<so>
 @(await Html.RenderComponentAsync<SalesOrderComponent>( new {sOrder = Model} ) )
</so>

Passing and Accepting Parameters
Inside the component, to accept the data in the anonymous object, you need a property whose name and type matches the property you created in the anonymous object. That property must also be decorated with the Parameter attribute. So, to accept whatever was passed in my previous code, I need this in my SalesOrderComponent (I've assumed that the Model object contains a SalesOrder object):

@code
{
  [Parameter]
  public SalesOrder sOrder { get; set; }

The earliest place where I can use that sOrder object is in my SalesOrder component's base OnParametersSet method. I might override that method to include code like this in my SalesOrderComponent:

private Customer cust;
protected override void OnParametersSet()
{
   cust = GetCustomerInfo(sOrder.CustomerId);
   base.OnParametersSet();
}

In my SalesOrderComponent, I could write out all the HTML to display my sales order information. However, it might make more sense for me to bring together a set of other, more dedicated components: One component to display customer information, another for ShippingAddress information, one more to display sales order header information, and one to display sales order detail information.

Assuming that I've already created a Customer, a ShippingAddress, a SalesOrderHeader, and a SalesOrderDetail component, my SalesOrderComponent might use them as child components like this:

<div>
  <Customer></Customer>
  <ShippingAddress></ShippingAddress>
</div>
<div>
  <SalesOrderHeader></SalesOrderHeader>
  <SalesOrderDetail></SalesOrderDetail>
</div>

Of course, I have to pass the information that each of those components needs to those components. That's actually easier to do from within a component than it is from a View or a Razor Page: I just add an attribute to each child component's element and set the attribute to the data I want to pass.

Revising my code to pass the Customer object to some of my components and the SalesOrder object to the others would give me code like this:

<div>
  <Customer custData=cust></Customer>
  <ShippingAddress shipCust=cust></ShippingAddress>
</div>
<div>
  <SalesOrderHeader salesorder=sOrder></SalesOrderHeader>
  <SalesOrderDetail salesOrder=sOrder></SalesOrderDetail>
</div>

Of course, to accept that data my Customer component will need a property called custData that's decorated with the Parameter attribute, my ShippingAddress component will need a property called shipCust (also decorated with the Parameter attribute), and the SalesOrderHeader and SalesOrderDetail components will each need a property called salesOrder (decorated with a Parameter attribute, of course).

Simplifying the Process
It's not impossible, however that my SalesOrderHeader and/or SalesOrderDetail components are, themselves, made up of child components (I bet, for example, that the SalesOrderDetail uses a Product component to handle displaying information about whatever product the customer has bought). I could, within the SalesOrderHeader and SalesOrderDetail, keep passing the SalesOrder down this chain of children.

Or I could use a cascading parameter which makes information available to any child component, no matter how deeply nested.

The first step in using a cascading parameter is to wrap my child components in a CascadingValue element and set the element's Value parameter to whatever I want to share. I don't have to, but I'm also going to set the CascadingValue's Name parameter to some string.

Here's my enhanced version of the part of my component that uses my two child components:

<div>
  <CascadingValue Value=sOrder Name="CascadingSalesOrder">
    <SalesOrderHeader></SalesOrderHeader>
    <SalesOrderDetail></SalesOrderDetail>
  </CascadingValue>
</div>

As you can see, this simplifies my code because my child components don't need attributes to accept the SalesOrder anymore.

To grab a cascading value in SalesOrderHeader or SalesOrderDetail, I just need to declare a parameter in the child component with the right type (SalesOrder, in this case) and decorate it with the CascadingParameter attribute. My SalesOrderHeader or SalesOrderDetail might have code like this, for example:

@code
{
  [CascadingParameter]
  private SalesOrder order { get; set; }

In fact, without any additional changes, a Product component that was called from the SalesOrderDetail component could declare a parameter like this to also grab the SalesOrder object. Once I start cascading a value in SalesOrderComponent, it carries on down through all the children.

Controlling the Process
I don't have to match property names with cascading values -- by default, cascading values are bound to the properties by type. This means that, as long as I have a property declared as SalesOrder (and decorated with the CascadingParameter attribute), it will pick up the SalesOrder being cascaded down through the various components. This will work fine ... as long as I have only one SalesOrder cascading down to my children.

I'd worry about that, which is why, back in my SalesOrder component, I gave my CascadingValue parameter a Name attribute. For any property accepting a cascading parameter, I can specify which SalesOrder I want by specifying the name of the CascadingValue parameter in my CascadingParameter attribute. That's a sentence that's harder to read than it is to do.

Here's what I mean: Back in my SalesOrder component, in the CascadingValue element, I set the Name attribute to "CascadingSalesOrder." I just repeat that name in the CascadingParameter in my SalesOrderHeader, SalesOrderDetail or Product component to make sure I get the SalesOrder from that CascadingValue. Code like this will do the trick:

@code
{
  [CascadingParameter(Name="CascadingSalesOrder")]
  private SalesOrder order { get; set; }

Sharing Changes
By the way, if I make changes to the SalesOrder object in my parent (the SalesOrderComponent), those changes are visible to the children (SalesOrderHeader and SalesOrderDetail). The reverse isn't true, however: Changes made in the children (SalesOrderHeader and SalesOrderDetail) are not visible to the parent (SalesOrderComponent).

In fact, if you'd rather that the children don't see the changes either, you can turn off that feature for CascadingValues by setting the element's IsFixed property to true. This, for example, would ensure that changes made to sOrder in SalesOrderComponent aren't visible in its children (SalesOrderHeader and SalesOrderDetail):

<div>
  <CascadingValue Value=sOrder Name="SoComponent" IsFixed="true">
    <SalesOrderHeader></SalesOrderHeader>
    <SalesOrderDetail></SalesOrderDetail>
  </CascadingValue>
</div>

This will also give you a bit of a performance boost because it turns off some monitoring.

I can, if I want, even cascade my whole component down into its children. I just set the Value attribute on the CascadingValue element to the keyword this. Of course, this would only be useful if my parent component has some public members that the child components could access.

Passing Parents to Children
Code in my SalesOrderComponent to pass itself to its children might look like this (I'm also having the SalesOrderComponent expose the cust property that holds the Customer object):

<div>
  <CascadingValue Value=this Name="SoComponent">
    <Customer></Customer>
    <ShippingAddress></ShippingAddress>
  </CascadingValue></SalesOrderDetail>
</div>

@code
{
   public Customer cust {get; set;}

If I go this route, then it is possible for children (the Customer and ShippingAddress components, in this case) to update any of the public properties on the parent object (the Customer property, for example). They just need a parameter like this to accept my SalesOrderComponent:

@code
{
  [CascadingParameter]
  public SalesOrderComponent soParent {get; set;}

Passing Content
Finally, there's a reason that component elements are made up of open and close tags rather than being a self-closing tag: You can also pass the content between the open and close tag of a component to a child component. This allows the parent component to provide some pre-defined UI for the child component to use (or not: just because the parent passes it doesn't mean the child has to use it).

In this example, I'm passing the sales order's Id inside a heading element to the SalesOrderHeader:

<SalesOrderHeader> <h3>@sOrder.Id</h3> </SalesOrderHeader>

To accept that content, all my SalesOrderHeader needs is a property called ChildContent, declared as RenderFragment and decorated with the [Parameter] element. I can display that content anywhere in my child's UI using the familiar @ symbol. This example will display the header I've passed in, just underneath the words "Sales Order:":

Sales Order: <br />
@sChildContent

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

Here, again, the name you give to the parameter property doesn't matter: You're only allowed one parameter of type RenderFragment and any content passed to your component will be shoved in there.

The current conventional wisdom is that it's better to have lots of focused objects than one big complicated one. These tools will let you break up your UI into single-purpose components that you can mix and match to create your user's experience.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube