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

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functions, scales, axis and shape ge

JavaScript new features in ES2019(ES10)

The 2019 edition of the ECMAScript specification has many new features. Among them, I will summarize the ones that seem most useful to me. First, you can run these examples in  node.js ≥12 . To Install Node.js 12 on Ubuntu-Debian-Mint you can do the following: $sudo apt update $sudo apt -y upgrade $sudo apt update $sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates $curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - $sudo apt -y install nodejs Or, in  Chrome Version ≥72,  you can try those features in the developer console(Alt +j). Array.prototype.flat && Array.prototype. flatMap The  flat()  method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth. let array1 = ['a','b', [1, 2, 3]]; let array2 = array1.flat(); //['a', 'b', 1, 2, 3] We should also note that the method excludes gaps or empty elements in the array: let array1

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.component