Angular Loading Spinner & Overlay | ng-hub-ui-loading

Angular loading component with spinner, dots, bars, pulse and ring indicators in inline, overlay and fullscreen modes, plus a service for app-wide blocking overlays.

Last updated Sep 5, 2026

Overview

Why teams search for this library

Use ng-hub-ui-loading when a section, a container or the whole application has to signal work in progress, without every view hand-rolling its own spinner and scrim.

Install

npm install ng-hub-ui-loading

Jump to

Ideal for

  • route and data loading
  • form submission overlays
  • app-wide blocking operations
  • branded splash screens
  • page progress bars under the navbar

About loading

ng-hub-ui-loading is a standalone Angular component that renders an indeterminate loading state in three modes: `inline` as a block in the document flow, `overlay` absolutely positioned over its parent container, and `fullscreen` fixed to the viewport. It ships five pure-CSS indicators — spinner, dots, bars, pulse and ring — in three sizes, an optional message and an optional brand image animated with `none`, `spin` or `pulse`. `HubLoadingService` drives the global fullscreen overlay programmatically and reference-counts concurrent callers, so parallel requests never tear the overlay away from each other. Every colour, size and duration is a `--hub-loading-*` custom property, with a `hub-loading-theme()` Sass mixin for wholesale restyling, and the component is accessible out of the box through `role=status`, `aria-live=polite` and `aria-busy`, honouring `prefers-reduced-motion`. Alongside it, `hub-loading-bar` reports that the page itself is on its way — the thin strip under the navbar, rendered in the document flow, attached to a positioned navbar or fixed to the viewport at `--hub-loading-bar-offset`. `HubLoadingBarService` reference-counts its callers in the same way, waits out a grace period so work that finishes quickly never flashes a bar, and trickles towards an end it never reaches on its own: only `complete()` may show 100%. `provideHubLoadingBarRouter()` and `hubLoadingBarInterceptor` wire it to navigation and to HTTP without either knowing about the other.

Feature guides

Inline Block

`inline` is the default mode because a region that is still empty should already reserve the space its content will take: the block joins normal document flow, so nothing jumps when the real content arrives. `message` renders under the indicator, and anything projected between the tags renders under the message — the place for a cancel action or a note that the operation is taking longer than usual. The host carries `role="status"`, `aria-live="polite"` and `aria-busy="true"`, so a screen reader picks the message up as it appears without interrupting whatever it is already reading.

Examples:
Inline Block Basics

The inline block in its three useful forms: indicator only, indicator with a message, and a message with a cancel action projected underneath.

Indicator only

Loading your workspace

With a message

Importing 1,248 rows

With projected content

The block is announced as role="status" with aria-live="polite", so a screen reader reads the message when it appears without interrupting whatever is being read.

Code
Import:
Template:
Component:

Indicators And Accents

Five pure-CSS indicators ship with the library — `spinner`, `dots`, `bars`, `pulse` and `ring` — so it pulls in no image asset and no icon font. Three size steps preset `--hub-loading-size`, and the token stays overridable on its own for a layout the steps do not cover. `color` is not an ordinary CSS colour: it goes through `resolveHubAccent()`, which reads a bare name such as `primary` as a design-system token that follows the theme, while a hex, an `oklch()` literal or a `var(...)` reference is used verbatim.

Examples:
Variants, Sizes And Accents

The five bundled indicators, the three size steps, and the three shapes an accent can take — a semantic name, a CSS colour literal and a `var(...)` reference.

Variants
spinner
dots
bars
pulse
ring
Sizes
size="sm"
size="md"
size="lg"

Each step is a preset for the --hub-loading-size token. Override that token directly when a layout needs a size the three steps do not cover.

Accents
color="primary"
color="success"
color="danger"
color="#7c3aed"
color="var(--hub-sys-color-warning)"

A bare name resolves to var(--hub-sys-color-<name>) and tracks the active theme. A hex, an oklch() or a var(...) is used verbatim.

Code
Import:
Template:
Component:

Overlay And Fullscreen

`overlay` covers one region instead of the page. It is positioned absolutely, so the nearest positioned ancestor decides what gets covered — give that container `position: relative`, or the overlay escapes upwards to whatever ancestor happens to be positioned. Scoping it that way keeps the rest of the screen scrollable and usable. `fullscreen` is fixed to the viewport and is what `HubLoadingService` mounts on `document.body`, outside every component subtree, so no ancestor's `overflow` or stacking context can clip it. Both modes paint the translucent scrim unless `backdrop` is turned off.

Examples:
Container Overlay

An overlay confined to one card by that card's `position: relative`, leaving the rest of the page scrollable. Toggle the backdrop to see the difference between a background refresh and work that invalidates what is on screen.

Order #4821

3 items · delivered 14 Aug 2026

Subtotal
212.00 €
Shipping
9.90 €
VAT (21%)
26.70 €
Total
248.60 €

Turn the backdrop off to keep the content readable underneath — useful when the work is a background refresh rather than something that invalidates what is on screen.

Code
Import:
Template:
Component:
Fullscreen Service

`HubLoadingService` driven from TypeScript. Two overlapping requests show why the reference counter matters: the first `hide()` leaves the overlay up because the second caller still holds a reference, and `update()` rewrites the message mid-flight.

The overlay covers the whole viewport, so this demo dismisses it for you: every request releases its own reference, and a watchdog forces a hideAll() after 5 seconds no matter what.
References held
0
isLoading()
false
Calls

Nothing yet.

Code
Import:
Template:
Component:

Branding And Theming

`image` swaps the built-in indicator for a mark of your own, animated with `none`, `spin` or `pulse`: `spin` only reads well on a radially symmetric mark, while `pulse` keeps a wordmark upright and legible. Everything visual lives in the `--hub-loading-*` tokens, declared on `:where(.hub-loading)` so any ancestor can set them — accent, size, speed, stroke thickness, gap, image size, text colour, and the backdrop tint and blur. The `hub-loading-theme()` Sass mixin sets the whole ladder in one call, and `provideHubLoading()` re-bases the defaults for an entire application without touching a template.

Examples:
Branded Image

A brand mark replacing the built-in indicator, shown with each of the three image animations so the trade-off between `spin`, `pulse` and `none` is visible.

Preparing your report

imageAnimation="none"

Preparing your report

imageAnimation="spin"

Preparing your report

imageAnimation="pulse"

Any URL works — an asset path, an imported file or, as here, an inline data URI. Size the mark with --hub-loading-image-size rather than size, which drives the built-in indicators.

Code
Import:
Template:
Component:
CSS Variable Theming

Live theming through the `--hub-loading-*` tokens set on a wrapper element, including the backdrop tint and blur that only do anything in `overlay` and `fullscreen` modes.

Inline — accent, size, speed, thickness

Overlay preview

The backdrop tint and blur only apply in overlay and fullscreen modes. The tint is mixed from --hub-sys-surface-page, so it veils this text with the theme's own surface instead of a fixed colour — drop it to zero and the card stays fully legible underneath.

Refreshing

The same overrides written once, in Sass, are in the CSS tab — that is what hub-loading-theme() is for.

Code
Import:
Template:
Component:

Page Progress Bar

`hub-loading-bar` is the thin strip that reports the page itself is on its way — the one under the navbar. Three decisions make it believable. It counts its callers, so a navigation and the three requests the page fires are four references and the bar finishes when the last one does, not the first. It waits before painting anything: work that finishes inside the grace period never shows a bar at all, because a 40 ms route that flashes a progress bar reads as a glitch rather than as speed. And its trickle decelerates as it fills, stopping short of the end — nothing here knows the real percentage, so only `complete()` may show 100%, because only `complete()` knows it is true. Place it with `mode="overlay"` inside a positioned navbar, or `mode="fixed"` and `--hub-loading-bar-offset` when the navbar is fixed too.

Examples:
Page Progress Bar

The bar hanging off a mock navbar. Three routes show the three behaviours: an ordinary one trickles, a slow one shows the trickle decelerating, and a 40 ms one never paints a bar at all.

Acme
Orders

This page arrived without you watching a spinner.

progress 0% · painted: false
Code
Import:
Template:
Component:
Known And Unknown Progress

Binding `progress` hands the bar to the caller and publishes `aria-valuenow`; `indeterminate` sweeps instead and withholds it. The third state, `null`, puts the bar away.

Known percentage
[progress]="null"

A real value, so the bar announces it. Reaching 100% is the caller's call, not the trickle's.

No percentage at all
indeterminate

A sweep instead of a fill, for a stream or a job that reports no stages. No value is announced, because there is none.

Both bars are mode="inline", so each reserves its own row and nothing on the card moves when a bar appears or goes away.

Code
Import:
Template:
Component:
Router And HTTP Wiring

Why the bar counts its callers: a navigation plus three requests are four references, and the bar finishes when the last one does. The code shows `provideHubLoadingBarRouter()`, the interceptor and the opt-out.

Dashboard
Callers
0
isActive()
false
progress()
0%
Calls

Nothing yet.

Code
Import:
Template:
Component:

Key features

Recent changes

Version 22.1.0 - 9/5/26, 12:00 AM

added: hub-loading-bar: the thin page-progress strip that sits under the navbar, in three placements — inline in the flow, overlay against a positioned ancestor, or fixed to the viewport at --hub-loading-bar-offset.

added: HubLoadingBarService, with reference-counted start() / complete(), an anti-flicker grace period so fast work never paints a bar at all — set delay to 0 to opt out and reveal synchronously — and a trickle that decelerates as it fills and stops short of the end, since only complete() may show 100%.

added: provideHubLoadingBarRouter(), which runs the bar for the length of a navigation, including one a guard rejects, and hubLoadingBarInterceptor with withoutHubLoadingBar() to keep polls and heartbeats out of the count.

added: Determinate mode through the progress input, which publishes aria-valuenow, and an indeterminate sweep. Both withhold the value while the number is invented, which is how ARIA marks a progressbar of unknown position.

added: provideHubLoadingBar(), the HUB_LOADING_BAR_CONFIG token, the exported hubLoadingBarTrickle() curve, thirteen --hub-loading-bar-* tokens and the hub-loading-bar-theme() Sass mixin.

Version 22.0.0 - 8/24/26, 12:00 AM

added: Initial release of hub-loading: one component covering the inline block, the container overlay and the fullscreen layer, with role="status", aria-live="polite" and aria-busy set on the host.

added: Five pure-CSS indicators — spinner, dots, bars, pulse and ring — in three size steps, with no image or font dependency.

added: Accent resolution through resolveHubAccent() from ng-hub-ui-utils: a bare name becomes a --hub-sys-color-* token and follows the theme, while a hex, an oklch() or a var(...) is used verbatim.

added: Optional image input with none, spin and pulse animations, replacing the built-in indicator with a brand mark.

added: HubLoadingService for the fullscreen overlay, with a reference counter so concurrent callers cannot unblock the screen out from under each other, plus update() to re-dress the live overlay and hideAll() to force the count to zero. SSR-safe: a no-op without a DOM.

added: provideHubLoading() and the HUB_LOADING_CONFIG token, supplying application-wide defaults to both the component and the service.

added: Theming through the --hub-loading-* token ladder and the hub-loading-theme() Sass mixin, shipped at ng-hub-ui-loading/styles.

Frequently asked questions

How do I show a loading spinner or overlay in Angular?

Install ng-hub-ui-loading and render `<hub-loading>` while the work is in flight. It defaults to an inline block in the document flow; set `mode` to `overlay` to cover the surrounding container, or to `fullscreen` to block the viewport. For an application-wide overlay, inject `HubLoadingService` and call `show()` and `hide()` around the operation.

Should I use a loading spinner, a skeleton or a progress bar?

Reach for ng-hub-ui-skeleton when you already know the shape of the content that is coming, because the placeholder reserves the space and avoids layout shift. Use ng-hub-ui-loading when the wait is indeterminate or the screen has to be blocked — a form being submitted, a report being generated. When you can report real completion, ng-hub-ui-metrics renders determinate bars, meters and rings instead.

How do I change the colour of the Angular loading indicator?

Set the `color` input, which accepts a semantic name, a hex literal, an `oklch()` value or a `var(...)` reference to one of your own tokens. For a permanent change, every colour, size and duration is a `--hub-loading-*` custom property you can redefine from your own stylesheet, and the `hub-loading-theme()` Sass mixin restyles the whole component in one place. The `image` input replaces the indicator with a logo when the loading state should carry the brand.

Is the Angular loading component accessible and SSR-safe?

Yes. The host is a live region — `role=status`, `aria-live=polite` and `aria-busy` — with an `ariaLabel` input for the announced text, and every animation is disabled under `prefers-reduced-motion`. On the server there is no DOM to mount into, so `HubLoadingService` keeps only its counter and server-side rendering stays safe.

How do I add a page loading bar under the navbar in Angular?

Render `<hub-loading-bar mode="overlay" placement="bottom">` inside a navbar that carries `position: relative`, or use `mode="fixed"` with `--hub-loading-bar-offset` when the navbar is fixed too. Then add `provideHubLoadingBarRouter()` so the bar runs for the length of each navigation, including one a guard rejects, and `hubLoadingBarInterceptor` so it also covers the requests the page fires on arrival. The bar reference-counts its callers, so the two compose without either knowing about the other, and `withoutHubLoadingBar()` keeps background polls out of the count.