Skip to main content
cngx-src documentation

CngxAsyncContainer

ComponentPrimaryOnPushNo encapsulationv0.1.0WCAG AA

projects/ui/feedback/async-container/async-container.ts

Import#

import { CngxAsyncContainer } from '@cngx/ui/feedback'

Description#

Async container molecule - coordinates all feedback states for data loading.

Projects four named templates and switches between them based on the CngxAsyncState lifecycle. Includes a built-in refresh indicator (bar) and ARIA state announcements.

<cngx-async-container [state]="residents">
  <ng-template cngxAsyncSkeleton>
    @for (i of [1,2,3]; track i) { <div class="skeleton-card"></div> }
  </ng-template>

  <ng-template cngxAsyncContent let-data>
    @for (r of data; track r.id) { <app-card [resident]="r" /> }
  </ng-template>

  <ng-template cngxAsyncEmpty>
    <cngx-empty-state title="No residents" />
  </ng-template>

  <ng-template cngxAsyncError let-err>
    <cngx-alert severity="error">{{ err }}</cngx-alert>
  </ng-template>
</cngx-async-container>

https://cngxjs.github.io/cngx/examples/#/forms/filter-builder/filter-builder-async-state/loading-error-content-branches-via-cngx-async-container https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/cngx-async-container-full-control-toast https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/cngxasync-one-line https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/cngxasync-with-custom-templates https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/composition-overlay-container-toast https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/createasyncstate-mutation https://cngxjs.github.io/cngx/examples/#/ui/feedback/async-container/injectasyncstate-reactive-query

Metadata#

Host#

Relationships

Index#

Inputs#

ariaLabel#string | undefined
input()

ARIA label for the region.

refreshIndicator#boolean
input()

Show refresh indicator bar during refresh/re-query.

default true
Required

The async state to render.

toastError#string | undefined
input()

Toast message on error. If set, fires a toast via CngxToaster.

toastSuccess#string | undefined
input()

Toast message on success. If set, fires a toast via CngxToaster.

HostBindings#

BindingExpression
[attr.aria-busy]state().isBusy() || null
[attr.aria-label]ariaLabel() || null

Default visuals for CngxAsyncContainer. The host carries .cngx-async-container but stays display: contents - the container delegates rendering to projected skeleton / content / empty / error templates. This stylesheet owns just two children: the absolutely-positioned refresh indicator and the sr-only announcement region.

Slots

  • .cngx-async-container__refresh - absolutely-positioned overlay anchored to the top edge, z-ordered above content (default 5) but below dialog / popover surfaces
  • .cngx-async-container__sr-only - visually-hidden live-region announcement target

Layout

--cngx-async-container-refresh-z#<number>
Default value 5

Stacking-context order of the refresh indicator overlay. Defaults to 5 so it sits above the content stream but below dialog / popover surfaces.

Data flow

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { injectAsyncState } from '@cngx/common/data';
import {
  CngxAsyncContainer,
  CngxAsyncContentTpl,
  CngxAsyncEmptyTpl,
  CngxAsyncErrorTpl,
  CngxAsyncSkeletonTpl,
} from '@cngx/ui/feedback';

/**
 * Async-container data flow — one signal, four view states.
 *
 * `injectAsyncState` wraps a simulated HTTP call that re-fires whenever
 * a tracked signal (the filter) changes. The returned `ReactiveAsyncState`
 * is fed into `<cngx-async-container>` via `[state]`, and four projected
 * templates (`cngxAsyncSkeleton`, `cngxAsyncContent`, `cngxAsyncEmpty`,
 * `cngxAsyncError`) render the appropriate view — no manual `@if` chains.
 * The first load shows the skeleton; subsequent refetches show the existing
 * content plus a top-bar refresh indicator. Empty results route to the
 * empty template; errors to the error template with the thrown value.
 */
@Component({
  selector: 'app-root',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    CngxAsyncContainer,
    CngxAsyncSkeletonTpl,
    CngxAsyncContentTpl,
    CngxAsyncEmptyTpl,
    CngxAsyncErrorTpl,
  ],
  template: `
    <p style="margin: 0 0 12px; opacity: 0.8; font-size: 0.875rem">
      Type into the filter — <code>injectAsyncState</code> re-fires the
      query, and <code>&lt;cngx-async-container&gt;</code> switches between
      skeleton, content, empty, and error views from a single
      <code>[state]</code> input.
    </p>
    <label style="display: flex; flex-direction: column; gap: 4px; max-width: 320px">
      <span>Filter names</span>
      <input
        type="text"
        [value]="filter()"
        (input)="filter.set($any($event.target).value)"
        placeholder="type to filter…"
      />
    </label>

    <cngx-async-container [state]="people" aria-label="People list">
      <ng-template cngxAsyncSkeleton>
        <p style="opacity: 0.6">Loading…</p>
      </ng-template>

      <ng-template cngxAsyncContent let-items>
        <ul>
          @for (item of items; track item) {
            <li>{{ item }}</li>
          }
        </ul>
      </ng-template>

      <ng-template cngxAsyncEmpty>
        <p>No results.</p>
      </ng-template>

      <ng-template cngxAsyncError let-err>
        <p style="color: #b00020">Error: {{ err }}</p>
      </ng-template>
    </cngx-async-container>
  `,
})
export class DataFlowExample {
  protected readonly filter = signal('');

  protected readonly people = injectAsyncState<string[]>(() => {
    const query = this.filter().toLowerCase();
    return new Promise<string[]>((resolve) =>
      setTimeout(() => {
        const all = ['Alice', 'Bob', 'Charlie', 'Diana'];
        resolve(query ? all.filter((n) => n.toLowerCase().includes(query)) : all);
      }, 1200),
    );
  });
}
export { DataFlowExample as AppComponent };