Schema.org Enumerations in TypeScript

Last time, we talked about modeling the Schema.org class hierarchy in TypeScript. We ended up with an elegant, recursive solution that treats any type Thing as a "@type"-discriminated union of ThingLeaf and all the direct sub-classes of the type. The next challenge in the journey of building TypeScript typings for the Schema.org vocabulary is modeling Enumerations.

Learning from Examples

Let’s look at a few examples from the Schema.org website to get a better sense of what Enumerations look like.

First up, I looked at PaymentStatusType, which can take any one of these values: PaymentAutomaticallyApplied, PaymentComplete, PaymentDeclined, PaymentDue, or PaymentPastDue. PaymentStatusType is used in the paymentStatus property on the Invoice class.

Here’s an excerpt from an example of an invoice:

{
    "@context": "http://schema.org/",
    "@type": "Invoice",
    // ...
    "paymentStatus": "http://schema.org/PaymentComplete",
    "referencesOrder": [
      // ...
    ]
}

Here, the value of an Enumeration appears as an absolute IRI.

Looking at other examples, however, such as GamePlayMode which appears in playMode on VideoGame shows up differently:

Continue reading “Schema.org Enumerations in TypeScript”

Modeling Schema.org Schema with TypeScript: The Power and Limitations of the TypeScript Type System

Recently, I published schema-dts (npm, GitHub), an open source library that models JSON-LD Schema.org in TypeScript. A big reason I wanted to do this project is because I knew some TypeScript type system features, such as discriminated type unions, powerful type inference, nullability checking, and type intersections, present an opportunity to both model what Schema.org-conformant JSON-LD looks like, while also providing ergonomic completions to the developer.

In a series of posts, I’ll go over some of the Structured Data concepts that lent themselves well to TypeScript’s type system, and those concepts that didn’t. First up: the type hierarchy of JSON-LD Schema.org Schema, and how can be represented in TypeScript.

Note: I’ll be describing JSON-LD in general in very broad strokes and will spend more time discussing how Schema.org JSON-LD looks like in particular. For those who are familiar with the JSON-LD spec, you’ll see I took a few liberties. This is because schema-dts makes a few assumptions, such as the @context being a known constant, etc. schema-dts also foregoes some features, such as specifying multiple types of a node object, etc.

Modeling the Schema.org class structure with the TypeScript Type System

Schema.org JSON-LD node objects are always typed (that is, they have a @type property that points to some IRI–a string–describing it). Given a @type you know all the properties that are defined on a particular object. Object types inherit from each other. For example, Thing in Schema.org has a property called name, and Person is a subclass of Thing that defines additional properties such as birthDate, and inherits all the properties of Thing such as name. Thing has other sub-classes, like Organization, with it’s own properties, like logo.

Let’s use this minimal example to try a few approaches:

1. Modeling each with inheritance

interface Thing {
  "name": string;
}
interface Person extends Thing {
  "@type": "Person";
  "birthDate": string;
}
interface Organization extends
    Thing {
  "@type": "Organization";
  "logo": string;
}

If we had a const something: Thing , then we could assign it to a Thing, Person, or Organization. So that’s a start! But there are a few problems:

Continue reading “Modeling Schema.org Schema with TypeScript: The Power and Limitations of the TypeScript Type System”

About those Side-effects in Observables, an Angular Use Case

When testing a codebase in Angular Ivy, I ran into a bunch of test failures I wasn’t seeing before. ExpressionChangedAfterItHasBeenCheckedErrors were being thrown around. In debugging these failures, I found that many of them are the results of side-effects in Observables and Observable pipe operations. I happened to describe these earlier in my piece on Observables, Side-effects, and Subscriptions.

Consider this minimal reproduction:

@Component({
  selector: 'widget-editor',
  templateUrl: 'widget_editor.html'
})
export class WidgetEditor {
  constructor(private readonly service: WidgetService) {}

  widgetName = '';
  widgetConfig$ = this.service.getWidget('my_widget').pipe(
    map(widgetDetails => {
      this.widgetName = widgetDetails.name;
      return widgetDetails.config;
    })
  );
}
Continue reading “About those Side-effects in Observables, an Angular Use Case”

Observables, Side-effects, and Subscriptions

My previous articles on using AsyncPipe and data refresh patterns in Angular hint at some common anti-patterns dealing with Observables. If there’s any common thread in my advice, it is: delay unpacking an Observable into its scalar types when performing logic you can rewrite as side-effect-free, leaving code with side-effects for subscription callbacks and other downstream logic.

My two earlier articles focused on cases users can benefit from handling more of the object’s lifecycle in its Observable form. In other words, cases where the Observable was being subscribed to and unpacked too soon. Instead, I suggested transforming the Observable using operators like map, switchMap, filter, etc. and taking advantage of the power offered by this form. In the case of Angular, it provides AsyncPipe, which takes the care of the step with side-effects (actually rendering the page) in template code.

There are some exceptions to this line of thinking, namely do and tap are reactive operators exclusively there for functions with side effects. I’ll leave a discussion of right vs less right reasons to use do/tap for a later article. But I’ll mention logging, error reporting, and caching of otherwise pure functions as one valid use of side-effects.

This article uses RxJS in code examples, but applies to broader reactive concepts.

Let’s explore a few of these cases:

1. Displaying data represented by Observables

Say I have two Observables wrapping some object in a storage format (e.g. JSON), and I’d like to display it.

Unpacking an observable too soon

let customerName: string;
let customerBalance: number;

nameObservable.subscribe(name => {
  customerName = name;
  if (customerName && customerBalance) {
    processAndDraw();
  }
});
balanceObservable.subscribe(balance => {
  customerBalancer = balance;
  if (customerName && customerBalance) {
    processAndDraw();
  }
});
function processAndDraw() {
  alert(`${customerName}: $${customerBalance.toFixed(2) USD`);
}

If a caller unpacks an observable too soon, it means they’re dealing with scalars, passing things around by global state. Developers might have trouble handling changes, such as adding a third data source to show.

Unpacking an Observable too late

combineLatest(nameObservable, balanceObservable).pipe(
  map(([name, balance]) => {
    alert(`${name}: $${balance.toFixed(2) USD`);
  })
).subscribe();

On the one hand, this is much shorter and more expressive! This is effectively maps Observable<[string, number]> into an Observable<void> which happens to perform side effects when subscribed to. The subscriber, however, has no idea what action will take place from just looking at a type or signature. Even with the code snippet above used as-is, it is very easy to forget about that last .subscribe() call, which–given that Observables are lazy by default and only perform useful actions when subscribed to–renders this whole snippet a no-op.

One final reason side-effects are bad in operators: that these side-effects can be performed an arbitrary number of times per event based on how many distinct subscribers are listening to an Observable.

A better trade-off

combineLatest(nameObservable, balanceObservable).pipe(
  map(([name, balance]) =>
    `${name}: $${balance.toFixed(2) USD`
  )
).subscribe(text => alert('Text'));

2. Avoiding Unnecessary Indirection through Subjects

In some ReactiveX implementation, a Subject is a powerful concept that allows an event publisher to share events with subscribers, as an Observable. It is also quite overused. Dave Sexton wrote a great piece in 2013 about whether or not to use a Subject, and further quoted Eric Meijer’s reasoning for disliking them:

[Subjects] are the “mutable variables” of the Rx world and in most cases you do not need them.

Erik Meijer, via To Use Subject or Not To Use Subject?

In particular, I’ve come across many examples in the wild violating Sexton’s first piece of advice, “What is the source of the notifications?” Here’s an egregious anti-pattern:

Continue reading “Observables, Side-effects, and Subscriptions”

Data and Page Content Refresh patterns in Angular

Part of why I recommend using RxJS Observables all the way through in Angular TypeScript code, and only unpacking them at the closest point to where the UI is declared (often using the | async pipe), is because it makes other transformations on an Observable available and convenient. Two such examples include retry and refresh logic.

Two common reasons to reload/refresh data being displayed by a component include:

  1. A user action in the application causes the data to change (especially, if it does so in ways that might result in complex state changes, etc.), and/or
  2. The data being displayed can change over time (e.g. due to a progression/change of it’s state in the backend, or by another user, etc.)

Let’s start with a simple example:

@Component({
  selector: 'task',
  template: `<ng-container *ngIf="(task$ | async) as task">
    <h1>{{task.name}}</h1>
    <p>Status: {{task.status}}</p>
    <sub-task *ngFor="let subTask of task.subtasks" [subTask]="subTask"/>`
})
export class TaskComponent {
  constructor(private readonly http: HttpClient) {}
  readonly task$ = this.http.get('/api/tasks/foo');
}

Suppose the user adds a ‘Mark as Complete’ button, that mutates the server-side state of all sub-tasks. How do obtain the latest authoritative data from the server about the state of our side? Here’s an approach:

export class TaskComponent {
  constructor(private readonly http: HttpClient) {}
  
  private readonly refreshToken$ = new BehaviorSubject(undefined);
  private readonly task$ = this.refreshToken$.pipe(
    switchMap(() => this.http.get('/api/tasks/foo')));

  markAsComplete() {
    this.http.post('/api/tasks/foo', { state: State.Done })
      // N.B. contrary to my advice elsewhere, I'm happy to
      // directly subscribe here because this subscribe
      // callback has side effects.
      // Further, I don't worry about unsubscribing since
      // this returned Observable is a one-shot observable
      // that will complete after a single request.
      .subscribe(() => this.refreshToken$.next(undefined));
  }
}

Adding refresh logic this way will minimally affect our template code and looks relatively clean. Better yet, adding additional mutating functions simply need to call refreshToken$.next to make sure new data is loaded.

What about regularly polling for updates? This can be implemented simply as well:

export class TaskComponent {
  constructor(private readonly http: HttpClient) {}
  
  private readonly autoRefresh$ =
    interval(TASK_REFRESH_INTERVAL_MS)
      .pipe(startWith(0));

  private readonly refreshToken$ = new BehaviorSubject(undefined);

  private readonly task$ =
    // Notice that combineLatest will only trigger the first
    // time when an event triggers on all input Observables
    // you are combining.
    // BehaviorSubject always triggers its latest value when
    // you subscribe to it, so we're good there.
    // An interval() Observable will need a 'startWith' to
    // give you an initial event.
    combineLatest(this.autoRefresh$, this.refreshToken$)
    .pipe(
      switchMap(() => this.http.get('/api/tasks/foo'))
    );

  markAsComplete() {
    this.http.post('/api/tasks/foo', { state: State.Done })
      .subscribe(() => this.refreshToken$.next(undefined));
  }
}

What if we didn’t want to hardcode foo as the task we look up? Well, Angular’s ActivatedRoute already uses Observables. Rather than using route.snapshot.params['task_id'] or similar, we can use the actual Observable results and get our minds off manually refreshing that data:

Continue reading “Data and Page Content Refresh patterns in Angular”
%d bloggers like this: