Skip to main content

Selective Angular Component Rendering with ngSwitch

Sometimes in Angular you have a collection of items and need to display a different component for different items in the same collection. Thankfully, Angular offers a directive called ngSwitch that gives us flexibility in how components render themselves. In this short article I'll show you an example from my game development project and how I used ngSwitch to work around it.

The Problem

In my particular case I’m building a text-based adventure game in Angular. I have a central region where story text will go, but not all text is equal. Some pieces of text will represent commands the player types in, some will represent story narrative, and some items representing general system status information.
I want these different types of text to be formatted differently, as pictured below:
The problem is that when you use ngFor to loop over a collection in Angular, the element the ngFor directive is on is the one that will be repeated. This makes it harder to represent content dynamically based on the values in the row.
To be more specific, I have an enum representing the different types of story text that are available:

export enum StoryEntryType {
    PlayerCommand,
    StoryNarrative,
    CommandError,
    SystemText
}
StoryEntryType.ts
The StoryEntry class represents the individual lines in the interactive story and looks like this:
import {StoryEntryTypefrom './StoryEntryType';

export class StoryEntry {
  EntryTypeStoryEntryType;
  Textstring;
  
  constructor(EntryTypeStoryEntryTypeTextstring) {
    this.EntryType = EntryType;
    this.Text = Text;
  }
}
StoryEntry.ts
This class is stored in a component as a collection like so:
@Input()
public Entries: StoryEntry[] = [];

So, now the problem comes in of how we do a ngFor over this collection in such a way that different EntryType values get rendered with different components.

The Solution: ngSwitch

The simplest and most maintainable solution I’ve found for this problem is to introduce a new component that takes in the StoryEntry object, then its template uses to conditionally render an additional component based on the EntryType of the entry.
ngSwitch lets you evaluate some condition inline in your HTML and render the element with a directive matching the switch condition via a ngSwitchCase directive. If no condition is matched, the default entry will be included instead if one is defined via the ngSwitchDefault directive.
This looks like this:
<div [ngSwitch]="Entry.EntryType">
<app-story-text *ngSwitchCase="Entries.StoryNarrative" [Text]="Entry.Text"></app-story-text>
<app-player-command *ngSwitchCase="Entries.PlayerCommand" [Text]="Entry.Text"></app-player-command>
<p *ngSwitchCase="Entries.SystemText" class="text-system">{{Entry.Text}}</p>
<p class="text-warn" *ngSwitchDefault>{{Entry.Text}}</p>
</div>

What’s especially noteworthy here is the value in the ngSwitchCase directive. We need to be able to reference an enum member by name in our HTML for maintainability. It turns out that in order to do that cleanly in Angular you need to do a little bit of a hack by defining an alias for your enum inside of the component like the following:
import {Component, Input, OnInit} from '@angular/core';
import {StoryEntry} from '../../Model/StoryEntry';
import {StoryEntryType} from '../../Model/StoryEntryType';
@Component({
selector: 'app-story-entry',
templateUrl: './story-entry.component.html',
styleUrls: ['./story-entry.component.scss']
})
export class StoryEntryComponent implements OnInit {
@Input()
public Entry: StoryEntry;
// Alias the enum so we can refer to its values in the component
public Entries = StoryEntryType;
constructor() { }
ngOnInit() {
}
}
This added complexity / hack from line 16 is unfortunate, but the best way I know of solving this problem. You could go away from enums entirely and use string values and string comparison, which removes this hack. However, if you did that, you lose the enum member safety that this solution provides.

End Result

This use of components, ngSwitch and using the enum alias gives us a fairly simple approach to conditionally displaying components for some types of entries versus others, which is what we set out to do.
Stay tuned as I continue to build out this adventure game over the coming month.

Originally published at https://killalldefects.com on February 6, 2020.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...