Comparing TagHelpers with View Components

The past couple of blogs have focused on Custom TagHelpers and ViewComponents. Each concept was introduced with .Net Core and both help simplify and clean up the way existing Razor Views are constructed.

Similarities

Even though TagHelpers and ViewComponents are their own independent implementation, they have some points in common.

Dependency Injection

ViewComponents is more well known for being the business logic piece between the two, but both still allow other classes to be injected.

TagHelper Dependency Injection:

public class ProductTagHelper : TagHelper { private readonly IProductService _productService;  public ProductTagHelper(IProductService productService) { _productService = productService; } }

ViewComponent Dependency Injection:

public class ProductViewComponent : ViewComponent { private readonly IProductService _productService;  public ProductViewComponent(IProductService productService) { _productService = productService; } }

Add HTML

ViewComponents don't alter a Razor View's HTML as it just adds to it. TagHelpers can add new HTML as well as alter HTML

Before: HTML calling a built-in .Net TagHelper

HTML calling a built-in .Net TagHelper

After: HTML calling a built-in .Net TagHelper

HTML calling a built-in .Net TagHelper

Before: Razor syntax calling a ViewComponent

Razor syntax calling a ViewComponent

After: HTML after ViewComponent rendered

HTML after ViewComponent rendered

Can be called with simple HTML, no Razor Syntax needed

To keep things simple, both TagHelpers and ViewComponents can be called using current HTML features. No Razor Syntax is needed. ViewComponents need to have the "vc:" prefix and the element name as a kebob of the ViewComponent name. TagHelpers can be either their own element or an attribute on an existing HTML element.

TagHelper being called as its own element

TagHelper being called as its own element

TagHelper being called as an attribute of an existing element

TagHelper being called as an attribute of an existing element

ViewComponent being called using C# razor syntax

ViewComponent being called using C# razor syntax

ViewComponent being called like a TagHelper

ViewComponent being called like a TagHelper

Note: If you are using .Net Core 1.0, this similarity will not exist as calling ViewComponents by HTML element structure is not supported. In that case (and as well still in .Net Core 1.1), you will need to use Razor View syntax.

Sync and Async Methods

TagHelpers and ViewComponents both have the ability to complete their work synchronously or asynchronously

TagHelper Methods

TagHelper Methods

ViewComponent being called like a TagHelper

ViewComponent Methods

Differences

Of course, there would be no need for different implementations if TagHelpers and ViewComponents didn't have their differences. Based upon these differences, you can choose which is the best implementation for you and what you are trying to accomplish.

TagHelpers

No lifecycle and no ViewState. Unlike when you call a controller's action method or ViewComponent, TagHelpers don't really have a lifecycle. When a razor view runs, any TagHelper get compiled into the output and the HTML is returned. It's as simple as that.

HTML Tags and Attributes

TagHelpers can be called by using new or existing HTML elements as well as utilizing attributes. By decorating a TagHelper class with the appropriate TargetElement attributes or RestrictChildren attributes, there can be more than a single way for a TagHelper to be called

HTML Tags and Attributes

Built into .Net Core

.Net Core comes with built-in TagHelpers already created, so for a good amount of functionality, you don't have to create anything new.

Form TagHelper: This TagHelper will even create an anti-forgery token

  • As Entered:

    Form TagHelpers As Entered
  • As Rendered:

    Form TagHelpers As Rendered

Select TagHelper: This TagHelper will populate all your dropdown options

Select TagHelper
  • As Entered:

    Select TagHelpers As Entered
  • As Rendered:

    Select TagHelpers As Rendered

Anchor TagHelper: This TagHelper will set your href

  • As Entered:

    Anchor TagHelpers As Entered
  • As Rendered:

    Anchor TagHelpers As Rendered

Environment TagHelper: This TagHelper will display the content within its tags when you are running your application in the environment specified

  • As Entered:

    Environment TagHelpers As Entered
  • As Rendered:

    Environment TagHelpers As Rendered

Note: These are just a few of the many built-in TagHelpers. For a comprehensive look into all of them, you should take a peek at them on GitHub

No Nesting but Multiples allowed

Since TagHelpers return HTML, they cannot new-up another TagHelper call (ie. return new HTML with a TagHelper call that was not already on the original Razor View). This example shows a "my-paragraph" TagHelper returning HTML with a "strong" TagHelper, but the strong TagHelper is not rendered. I've included a real "strong" TagHelper to show that it does work

  • As Entered:

    Multiple TagHelpers As Entered
  • As Rendered:

    Multiple TagHelpers As Rendered

A single HTML element can have multiple TagHelpers. Since TagHelpers run synchronously on the same element and the result of the first can impact the result of the second, you should specify the order they are to be constructed.

Multiple TagHelpers Code
  • As Entered:

    Bold TagHelper As Entered
  • As Rendered:

    Bold TagHelper As Rendered
Existing Attributes and Child Content

Regardless of if you are using a TagHelper defined HTML element or an existing HTML element, you can grab and utilize the attributes on the element as well as the child content of the element.

Existing Attributes and Child Content
Existing Attributes and Child Content Code

You can also specify content to be added before or after the child content.

Existing Attributes and Child Content Code

You can even suppress the html from even showing based upon particular parameters.

Existing Attributes and Child Content Code
Existing Attributes and Child Content Code
No Templates

TagHelpers don't allow you to use another Razor View and plug data into it. Instead data must be constructed pretty much through string implementation. This can get hairy if you are trying to create a large chunk of view. Therefore, I feel that TagHelpers are best suited for small sets of HTML.

No Template Code

If you feel you have to compose a large chunk of complex HTML, the best approach is to break it up, let TagHelpers construct small chunks of HTML, and then let a "wrapper" TagHelper put it all together

ViewComponents

Nesting allowed

Since ViewComponents go through a full Razor View lifecycle of being compiled, a Razor View can call a ViewComponent whose own Razor View can call another ViewComponent. Also, with inheriting from ViewComponent, your ViewComponent is being given the majority of ViewContext you would be given through a regular MVC Controller call

  • The Controller View:

    ViewComponent - The Controller View
  • The Widget ViewComponent View:

    ViewComponent - The Widget View
  • The HelloWorld ViewComponent View:

    The HelloWorld ViewComponent View
  • Rendered View:

    ViewComponent Rendered View
Data View Size

ViewComponents typically compose larger chunks of data than TagHelpers. As we've seen though, multiple TagHelpers together can compose large chunks of HTML as well. Therefore, along with the typical size of data rendered, ViewComponents are a typical place for business logic to be performed.

Run-time errors

ViewComponents can cause run-time errors, unlike TagHelpers which can exist on a Razor View even if they don't exist.

ViewComponent Runtime Error

There are exceptions to this rule.

  • You can call your ViewComponent as a TagHelper. If the ViewComponent doesn't exist, then nothing is rendered.

    ViewComponent Exception Rule
  • You can also call your ViewComponent as a Type instead of a string. This way if the ViewComponent doesn't exist you will get a compile time error.

    ViewComponent Exception Rule
Where to Invoke ViewComponents

Unlike TagHelpers which are called strictly from the RazorView, ViewComponents can be called both from a RazorView, a controller's action method, or a WebApi Method

Where to Invoke ViewComponents
Same call, different results

Given a ViewComponent that is called with the exact same inputs, the exact same result will not occur every time. This result is due to how views are searched for. Views are search first by "Views\{ControllerName}\Components\{ComponentName}\Default.cshtml" and then by "Views\Shared\Components\{ComponentName}\Default.cshtml". Whichever is found first, is used. Therefore, if you have two different controller Index.cshtml views and call the same ViewComponent with the exact same parameters, you can possibly get different results

The ViewComponent that is called:

ViewComponent Code

Folder Structure of where ViewComponent views:

ViewComponent Folder Structure

Call to ProductItemViewComponent from ProductController's razor view and rendered html:

ViewComponent Code
ViewComponent Result

Call to ProductItemViewComponent from CartController's razor view and rendered html:

ViewComponent Code
ViewComponent Result
Summary

Overall which concept to use depends not only on what you are trying to do but also who is trying to do it. For someone who comes from a C#, .Net background, ViewComponents are probably the preferred path as they incorporate a lot more business logic and look like previous Child Actions. Whereas someone who is more UI centric will prefer TagHelper as he might need to know a little C# code, but doesn't need to know all the code that is necessary for ViewComponents.

Another way to determine which item to use is based upon the reusability and flexibility to make changes to the rendered output. ViewComponents can be looked at as once you render some HTML it is rendered. TagHelpers on the other hand encapsulate small bits of view logic and ensure consistent generation of HTML across the site. This can be viewed as putting a layer of HTML on top of another layer. If you want to add another layer, you just add that additional TagHelper to the appropriate element.