Exploring Angular's Hidden Template Gems

Angular 12 recently introduced the nullish coalescing operator ('??') for use in templates. Beyond this addition, Angular templates conceal a variety of lesser-known features that many developers haven't encountered. Let's delve into these hidden capabilities!

Leveraging ngProjectAs

Angular's content projection mechanism bears resemblance to the Web Components slot approach. While a simple <ng-content></ng-content> will project all content placed within your component tag, you can implement multiple content tags with the "select" attribute for precise targeting. Consider this illustrative example:

@Component({
  selector: 'layout',
  template: `
    <ng-content select="header"></ng-content>
    <main>
      <ng-content select="aside"></ng-content>
      <ng-content></ng-content>
    </main>
    <ng-content select="footer"></ng-content>
  `,
  styles: [`
    :host {
      height: 100%;
      display: flex;
      flex-direction: column;
    }

    main {
      display: flex;
      flex: 1;
    }
  `]
})
export class LayoutComponent {}

You can supply sections to this layout component leveraging content projection as demonstrated:

<layout>
  <header>Header</header>
  <aside>Sidebar</aside>
  <footer>Footer</footer>
  I am content
</layout>

What if you need to project content without introducing additional DOM elements? For instance, you might want multiple tags directed to a single slot or even project custom text. The ng-container combined with ngProjectAs accomplishes this efficiently. Take a look at the demonstration below:

https://stackblitz.com/edit/angular-content-selection

The angular.io documentation recently incorporated this into the content projection guide. If you're seeking deeper insights, that resource is definitely worth diving into.

Understanding ngNonBindable

Imagine you need to showcase an interpolation example with handlebars in your template, displaying something like this verbatim: <div>Hello, {{userName}}</div>. Without appropriate escaping, Angular would attempt literal processing. Inserting a single { in your template triggers this warning:

Do you have an unescaped { in your template? Use {{ '{' }}") to escape it.

Writing a series of handlebars rather than just one seems impractical. Although you could experiment with a custom interpolation symbol at the component level, this approach won't fully address your needs — that configuration introduces an alternative but lacks replacement of the default symbol.

ngNonBindable arrives as the perfect solution here. Acting as a compiler directive, similar to i18n, it signals Angular's compiler to treat the designated portion of the template as raw HTML: <div ngNonBindable>Hello, {{userName}}</div>

Managing Whitespace with ngPreserveWhitespaces

Angular assists developers by stripping unnecessary whitespace from templates during the compilation process. If you'd prefer to disable this optimization globally, adjust the angularCompilerOptions within your tsconfig.json. For targeted control, apply the @Component decorator configuration on a per-component basis. While this feature proves typically useful, there are scenarios where you'll want to keep whitespace intact for specific fragments. Another compiler directive handles this — placing ngPreserveWhitespaces on an element preserves all its whitespace during compilation.

The &ngsp; Helper

Continuing from the previous discussion, there are times when an additional space must be consciously retained within your template. For such needs, you can employ a distinctive symbol originating from Angular Dart, believe it or not — &ngsp. This symbol withstands whitespace purging and gets converted into a standard space during compilation.

Working with $any()

Have you grappled with type-related issues from a third-party library? Suppose you're dealing with immutable data and need to pass your array to a component input. Yet the type is declared as an Array, denying mutations. TypeScript would flag issues, indicating your readonly instance lacks certain mutable methods and showing errors. Additionally, interfaces might sometimes be inaccurately specified if the maintainer isn't an avid TypeScript user. Here's an actionable pair of steps:

  1. Enclose your object using the $any() special function: [value]=”$any(value)”
  2. Visit that third-party library's GitHub page and submit a pull request addressing the type problems

Keep in mind — this technique only yields results when both actions are performed together!

Discovering Bindon

Feeling overwhelmed by all the special characters in standard binding syntax and finding it unclear?

<my-component
  #component
  [@animation]=”animation”
  [value]=”value”
  [(banana)]=”twoWayBoundValue”
  (output)=onOutput($event)”
  (click)=onClick()”
></my-component>

There exists an alternate syntax for bindings. You can achieve the same functionality without the bracket clutter:

<my-component
  ref-component
  bind-animate-animation=”animation”
  bind-value=”value”
  bindon-banana=”twoWayBoundValue”
  on-output=onOutput($event)”
  on-click=onClick()”
></my-component>

Honestly, I remain uncertain why one would opt for this syntax, perhaps a desire to categorize bindings by type? Regardless, this approach serves as a viable option, and now you're in the know!

Unexpected Extra

Here's a nugget worth noting — Angular templates support additional built-in tags beyond the standard trio of ng-template, ng-container, and ng-content. There's also ng-component. While not truly a template feature, encountering it in DevTools while examining rendered templates sparked my curiosity enough to include it here.

Can you hypothesize why it emerges in the HTML following page rendering? A hint for you: the @Component decorator allows for an optional selector:

@Component({
   selector: 'app-root', ←---- this thing is optional
   ...
})

Consider what Angular substitutes for components lacking this property declaration. It's precisely in those situations you'd discover ng-component — an auto-generated tag for components without a defined selector. You might wonder how components without selectors materialize in HTML. Well, dynamic instantiation via *ngComponentOutlet is one route; additionally, the Router can insert such components following the router-outlet!

Are you aware of additional template peculiarities omitted from this overview? Please share them in the comments section. I've yet to encounter a definitive collection of these features in one place, and this article serves as an amalgamation of personal findings. Thanks for reading!