Angular

Angular Components with Non-Standard Selectors

When I teach Angular, I keep telling my students that Angular components are custom HTML elements, while Angular directives are custom attributes. Every tutorial I know teaches the same. Nonetheless, Angular is much more flexible. In reality, the selector of both components and directives can be any CSS selector. For instance, you are allowed to define a component like so:

@Component({
  selector: '[framed-image]',
  templateUrl: './framed-image.component.html'
})
export class FramedImageComponent { ... }

This component is used like so:

<div framed-image="art-deco" src="somePainting.jpg"></div>

What to make of this? What’s the difference between such a component and a directive?

What does the Angular style guide say?

As it turns out, the Angular style guide discourages this coding style. The general idea is to use precisely what I said above: use components to generate chunks of HTML code, and define them as custom HTML elements. Use directives to modify the behavior or the appearance of an existing HTML element, and use an attribute to activate the directive.

By the way, that’s a bit different to the jQuery tradition. jQuery components usually use CSS classes or custom attribute as a marker interface. For example, Bootstrap 3 uses the attributes data-toggle, title, and data-content to add a popover to an arbitrary HTML element. When jQuery was invented, this was the only way to implement custom components. Angular is a bit younger, so it’s heavily inspired by the idea of the WebComponents.

What happens when using an attribute selector in Angular?

The traditional mental model of a component is that the HTML code provided in the template string simply is the component.

However, a quick glance at the HTML code in the browser’s developers tool reveals a slightly different truth. Angular always retains the original element in the DOM tree. The resulting HTML code is nested below. That doesn’t hurt because the browser simply ignores HTML tags it doesn’t know. But it does render HTML code nested inside the unknown HTML tag. So it doesn’t make a difference whether there’s an unknown HTML element somewhere in the DOM tree. Angular 1.x used to offer an option to remove the custom HTML element from the DOM, but since Angular 2, this option has been dropped because it doesn’t make a difference, anyway.

When do we need attribute selectors in Angular?

Only sometimes it does. Almost always the reason is called “CSS”.

For instance, Material Design has a lot of CSS rules linked to standard HTML elements, such as button. So it makes sense to implement the component using the attribute syntax. The resulting component is the standard Material Design component (basically defined by the CSS files of Material Design), plus some additional behavior. The component can also add HTML code that’s going to be nested in the standard button.

Another popular reason to use attribute selectors is that many CSS libraries don’t play well with the way Angular implements components. For instance, consider the NavBar of Bootstrap. The raw HTML code of such a NavBar (aka the main menu) looks like so:

<ul class="navbar-nav mr-auto">
   <li class="nav-item active">
     <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
   </li>
   <li class="nav-item">
     <a class="nav-link" href="#">Link</a>
   </li>
</ul>

It’s tempting to encapsulate the NavBar in a <navbar> component containing several <navlink components. Unfortunately, using the default approach of Angular, the resulting HTML code looks like so:

<navbar>
  <ul class="navbar-nav mr-auto">
    <navlink>
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
    </navlink>
    <navlink>
      <li class="nav-item">
        <a class="nav-link" href="#">Link</a>
      </li>
    </navlink>
  </ul>
</navbar>

Few CSS frameworks are prepared to deal with the additional custom HTML elements. In our case, Bootstrap expects the NavLink to be a direct child of the NavBar. However, Angular renders it as a grandchild.

Wrapping it up

Non-standard CSS selectors allow you to solve problem that are difficult using the standard approach. However, I recommend to stick to use element selectors for Angular components as long as possible. If you feel the need to use attribute selectors, read the documentation covering CSS in Angular components again. More often than not, you’re simple looking for :host, :host-context or /deep/.

However, if you turn out to be looking for /deep/, you are really in trouble because it’s one of the deprecated APIs of Angular. In this case, using a non-standard selector may really be the solution.