Logo

dev-resources.site

for different kinds of informations.

Forcing Angular SSR to Wait in 2024

Published at
7/10/2024
Categories
angular
webdev
javascript
async
Author
jdgamble555
Categories
4 categories in total
angular
open
webdev
open
javascript
open
async
open
Author
11 person written this
jdgamble555
open
Forcing Angular SSR to Wait in 2024

Angular has had a built-in way to wait on your functions to load, and you didn't know about it!

In the past...

You needed to import the hidden function...

import { ɵPendingTasks as PendingTasks } from '@angular/core';
Enter fullscreen mode Exit fullscreen mode

Notice the greek letter that you wouldn't normally find with autocomplete.

Today

It is experimental, but you will soon be able to just import PendingTasks.

import { ExperimentalPendingTasks as PendingTasks } from '@angular/core';
Enter fullscreen mode Exit fullscreen mode

Setup

I use my useAsyncTransferState function for hydration. This ensures an async call, a fetch in this case, only runs once, and on the server.

export const useAsyncTransferState = async <T>(
    name: string,
    fn: () => T
) => {
    const state = inject(TransferState);
    const key = makeStateKey<T>(name);
    const cache = state.get(key, null);
    if (cache) {
        return cache;
    }
    const data = await fn() as T;
    state.set(key, data);
    return data;
};
Enter fullscreen mode Exit fullscreen mode

Token

We need reusable tokens for the REQUEST object.

// request.token.ts

import { InjectionToken } from "@angular/core";
import type { Request, Response } from 'express';

export const REQUEST = new InjectionToken<Request>('REQUEST');
export const RESPONSE = new InjectionToken<Response>('RESPONSE');
Enter fullscreen mode Exit fullscreen mode

We must pass the request object as a provider in our render function.

// main.server.ts

export default async function render(
  url: string,
  document: string,
  { req, res }: { req: Request; res: Response }
) {
  const html = await renderApplication(bootstrap, {
    document,
    url,
    platformProviders: [
      { provide: REQUEST, useValue: req },
      { provide: RESPONSE, useValue: res },
    ],
  });

  return html;
}
Enter fullscreen mode Exit fullscreen mode

Angular is currently in the process of adding all of these features, and potentially endpoints!!!!! 😀 😀 😀 🗼 🎆

Fetch Something

Because endpoints are not currently there, I am testing this with Analog. Here is a hello endpoint that takes 5 seconds to load.

import { defineEventHandler } from 'h3';

export default defineEventHandler(async () => {

    const x = new Promise((resolve) => setTimeout(() => {
        resolve({
            message: "loaded from the server after 5 seconds!"
        });
    }, 5000));

    return await x;

});
Enter fullscreen mode Exit fullscreen mode

Test Component

Here we use the request in order to get the host URL. Then we use useAsyncTransferState to ensure things only run on the server, and only once. Finally, we use pendingTasks to ensure the component is not fully rendered until the async completes.

import { AsyncPipe } from '@angular/common';
import {
  Component,
  ExperimentalPendingTasks as PendingTasks,
  inject,
  isDevMode
} from '@angular/core';
import { REQUEST } from '@lib/request.token';
import { useAsyncTransferState } from '@lib/utils';


@Component({
  selector: 'app-home',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <div>
      <p class="font-bold">{{ data | async }}</p>
    </div>
  `
})
export default class HomeComponent {

  private pendingTasks = inject(PendingTasks);

  protected readonly request = inject(REQUEST);

  data = this.getData();

  // fetch data, will only run on server
  private async _getData() {
    const schema = isDevMode() ? 'http://' : 'https://';
    const host = this.request.headers.host;
    const url = schema + host + '/api/hello';
    const r = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
      }
    });
    const x = await r.json();
    return x.message;
  }

  // fetch data with pending task and transfer state
  async getData() {
    const taskCleanup = this.pendingTasks.add();
    const r = await useAsyncTransferState('pending', async () => await this._getData());
    taskCleanup();
    return r;
  }

}
Enter fullscreen mode Exit fullscreen mode

Pending Task

Pending Task is very simple.

// create a new task
const taskCleanup = this.pendingTasks.add();

// do something async
const r = await fn();

// let Angular know it can render
taskCleanup();
Enter fullscreen mode Exit fullscreen mode

Thats it! Bingo Shabongo!

Repo: GitHub
Demo: Vercel Edge - Takes 5s to load!

Should you use this?

Nope! Seriously, don't use this.

After going down the rabbit hole for years on Angular async rendering (read the old posts in this chain), it is definitely best practice to put ALL async functions in a resolver. The resolver MUST load before a component, which is a much better development environment. The only exception would be @defer IMHO.

However, there are some edge cases where it makes sense for your app. The is particularly evident when you don't want to rewrite your whole application to use resolvers. Either way, you need to be aware of your options!

J

async Article's
30 articles in total
Favicon
This Small Python Script Improved Understanding of Low-Level Programming
Favicon
Async,Await Promise
Favicon
Async Vs Sync, which is most preferrable?
Favicon
Async/Await: Task.WhenAll + Exceptions = Dor de Cabeça!
Favicon
Everything You Need to Know About JavaScript Promises and How They Work
Favicon
Asynchronous Python
Favicon
Building pipelines with IAsyncEnumerable in .NET
Favicon
Unleash the Power of FastAPI: Async vs Blocking I/O
Favicon
Total Madness #2: Async Locks
Favicon
Don't use 'BuildContext's across async gaps.
Favicon
Integration Digest: May 2024
Favicon
Total Madness #1: Async/Await
Favicon
Forcing Angular SSR to Wait in 2024
Favicon
Using Async in Ruby on Rails for CSV export
Favicon
Mastering Async Await in JavaScript for Asynchronous Programming
Favicon
PHP HyperF + MariaDB -> Async / Parallel
Favicon
Async/await and SwiftUI
Favicon
🕒 Task vs Promise: Chaining
Favicon
🕒 Task vs Promise: Encadenación
Favicon
New custom blocks for Analytics Builder (async comms, downsampling and complex measurements)
Favicon
Concurrent-ruby (async) S3 files download
Favicon
Ruby class pattern to work with API requests with built-in async approach
Favicon
How to use ActionCable with async requests in a Ruby on Rails web app
Favicon
Introducing EventSail: A Python Library for Event-driven Programming
Favicon
Enhancing Asynchronous Data Fetching in Umbraco v14 with Lit Async Directives
Favicon
API simples que gera arquivos de forma assíncrona, com Java e Spring? Aqui tem!
Favicon
Async Axiom logging
Favicon
Rust: Actix-web -- Async Functions as Middlewares
Favicon
Streamlining Asynchronous Tasks in Django with Django Tasks Scheduler
Favicon
JavaScript async call analysis

Featured ones: