Angular ポータルとオーバーレイレンダリング | ng-hub-ui-portal

現在の DOM ツリーの外側にコンポーネント、テンプレート、オーバーレイを動的にレンダリングする Angular ポータルライブラリ。位置制御に対応します。

最終更新 2026/09/20

概要

チームがこのライブラリを探す理由

この Angular ポータルライブラリは、データフローと Angular との統合を予測可能に保ちながら、コンテンツをオーバーレイやカスタムコンテナーにレンダリングできるよう支援します。

インストール

npm install ng-hub-ui-portal

ジャンプ

最適な用途

  • ドロワー
  • フローティングパネル
  • オーバーレイ UI
  • 動的コンテナー

について portal

ng-hub-ui-portal は、Angular インターフェースがレンダリングを現在の DOM ツリーから切り離す必要があるときに役立ちます。ドロワー、フローティングパネル、動的コンテナー、オーバーレイベースの UI などです。プロジェクションされたコンテンツと位置指定レンダリングのためのよりクリーンな基盤をチームに提供します。

機能ガイド

コンテンツのレンダリング

データ注入と投影機能を備え、さまざまなコンテンツタイプ(Component、TemplateRef、String)のレンダリングをサポートします。

サンプル:
コンポーネントレンダリング

コンポーネントクラスでポータルを開き、返された参照からインスタンスにアクセスします。

コード
Import:
Template:
Component:
TemplateRef レンダリング

ng-template をポータルの内容としてレンダリングし、close と dismiss をテンプレートのコンテキストで受け取ります。

コード
Import:
Template:
Component:
文字列コンテンツ

伝えることが一つだけのポータルのために、単なる文字列を内容として渡します。

コード
Import:
Template:
Component:
データの受け渡し

独自の Injector で内容コンポーネントにデータを渡し、クローズ時の値から答えを受け取ります。

コード
Import:
Template:
Component:

開く戦略

ポータルの開き方を制御します:プログレッシブ(スタック)モードまたは排他的(トグル)モード。

サンプル:
段階的に開く(スタック)

open() でポータルを積み重ね、いくつ開いたままかをサービスに問い合わせます。

Active Portals: 0
コード
Import:
Template:
Component:
切り替え(排他的)

toggle() は開いているものを閉じ、非表示になるまで待ってから、新しいポータルを表示します。

Active Portal: None
コード
Import:
Template:
Component:

コンテナの指定

ポータルのウィンドウを追加する先を選べます。既定ではドキュメントの body、あるいは CSS セレクターで指定した要素や HTMLElement として渡した要素です。

サンプル:
カスタムコンテナ(旧)

ウィンドウを別のコンテナへ送り、windowClass で装います。配置はこのデモ自身の CSS によるもので、ライブラリは追加するだけで配置はしません。

Configuración de Posicionamiento
Elementos de Referencia
Top LeftEsquina superior izquierda
Top RightEsquina superior derecha
CenterElemento central
Bottom LeftEsquina inferior izquierda
Bottom RightEsquina inferior derecha
Targets de Portal
Target 1
Target 2
Target 3
Configuración Actual
Posición: absolute
Target: body
Offset: X: 0px, Y: 0px
Backdrop: No
コード
Import:
Template:
Component:

オーバーレイ管理

ポータルが開いている間、背後のページはスクロールしなくなり、フォーカスは最前面のウィンドウに閉じ込められ、その外側にはすべて aria-hidden が付きます。

サンプル:
基本的な使い方(旧)

サービスのふだんの使い方。ポータルを開き、result を待ち、値を添えて閉じます。

Portal Controls

Content is rendered dynamically into the DOM (typically body) using the HubPortal service, completely detaching it from this component's DOM hierarchy while preserving Angular context.

コード
Import:
Template:
Component:

インタラクション

結果を伴うポータルのクローズ/ディスミス、キーボードサポート、カスタムトリガーセレクターに対応します。

サンプル:
コンテンツプロジェクション

headerSelector と footerSelector オプションで、内容をヘッダー・本文・フッターに振り分けます。

コード
Import:
Template:
Component:
コンポーネントレンダリング

コンポーネントクラスでポータルを開き、返された参照からインスタンスにアクセスします。

コード
Import:
Template:
Component:

ライフサイクル

ポータルの状態管理のためのライフサイクルフックとイベントサブスクリプションです。

サンプル:
サービス & インジェクション(旧)

サービスから操るポータル。トースト、ダイアログ、ドロワーに加えて dismissAll、hasOpenPortals、activeInstances も扱います。

Gestión Programática de Portales
Notificaciones Toast
Modales Dinámicos
Overlays Complejos
Gestión Global
Estado del Sistema de Portales
Portales Activos:0
Último Portal:Ninguno
Z-Index Actual:1000
Auto-increment ID:1
コード
Import:
Template:
Component:
段階的に開く(スタック)

open() でポータルを積み重ね、いくつ開いたままかをサービスに問い合わせます。

Active Portals: 0
コード
Import:
Template:
Component:

主な機能

最近の変更

Version 22.2.2 - 9/20/26, 12:00 AM

changed: The npm keywords declare ng-hub-ui, the family name somebody searching for the ecosystem types. Metadata only: no code, types or styles change.

Version 22.2.1 - 9/16/26, 12:00 AM

changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.

Version 22.2.0 - 9/8/26, 12:00 AM

fixed: HubPortal.open() with a plain string opened an empty dialog. Every other kind of content is split into the three slots the window destructures, header, body and footer, but the string path returned a single slot: the text landed in the header and the body arrived undefined, which Angular projects as nothing, so the reader saw a blank box. The string now goes into the body slot like everything else, and four specs pin it. Worth stating plainly, because the same defect behaved worse in ng-hub-ui-modal, where it also took the keyboard down with it: here Escape never stopped working, because this window arms its listeners in ngOnInit rather than at the tail of the entry transition.

changed: The body mark is hub-portal-open. The old portal-open claimed a name in the application namespace rather than in the library one, the same defect ng-hub-ui-utils retired from the bare [tooltip] attribute in 22.14.0, and nothing warns a host whose own .portal-open rule is silently joined by ours. Both classes are written for now, so a stylesheet matching the old name keeps working; portal-open is removed in 23.0.0. See BREAKING_CHANGES.md.

changed: scrollable is delivered by the dialog rather than by a class on the content component host. That host is only a query root: its children are handed to the window and the host itself never enters the document, so component-host-scrollable, and the only rule this library shipped for it, matched nothing and asking for scrollable did nothing at all. The option now reaches the dialog through portal-dialog-scrollable, which the window already set, and the stylesheet dresses that class: the content box is pinned and the body scrolls inside it, or, where the content brings no .portal-body of its own, the content box is what scrolls.

changed: The slot split is one documented function instead of three inline expressions. Both declared slots are taken out of the container before the body is captured, and the body is captured as a static array. The old code read the body between the two extractions and got away with it only because what it captured was the live childNodes list, which Angular snapshots later, once both markers are already gone: correct by accident, disagreeing with the Node[][] ContentRef declares, and going empty the moment the nodes are projected out. No behaviour changes, and a spec now guards the contract.

added: Both READMEs state where this library stands on server-side rendering. The honest answer is not verified: a portal window only exists after a gesture, so the site prerender, which is the running proof for the libraries that render markup on the page, never draws one.

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

changed: Dropped the angular16 keyword from the package manifest. The peer range has required Angular 18 or newer since 22.0.1, so the keyword was advertising the package to exactly the searchers whose install it would refuse.

changed: HubPortalRef is generic, so opening a portal no longer costs you the types. HubPortal.open<C, R>() and toggle<C, R>() infer C from the class handed to them, which is what makes componentInstance the component instead of any, while R types the value that travels through close(), result and closed. Until now the README answered this with a hand-written cast that claims a type nobody checks. Both parameters default to any, but componentInstance narrows from any to C | void, so a call site that reached straight through it stops compiling: see BREAKING_CHANGES.md. content is now typed Type<C> | TemplateRef<any> | string, which is what the stack already accepted, and the whole shape mirrors what ng-hub-ui-modal settled on in 22.5.0. Dismiss reasons stay untyped on purpose, because they carry either an internal PortalDismissReasons value or whatever the consumer passed.

removed: Removed the unused BACKDROP_ATTRIBUTES constant, a commented-out import in portal-config.ts and two commented-out lines of an older toggle() implementation. None of it was reachable, and backdropClass, the option that constant named, is not part of HubPortalOptions, so leaving it in suggested a backdrop API the library does not have. No behaviour changes.

fixed: Escape now dismisses the portal, as the keyboard option has always promised. The option was declared, defaulted to true in HubPortalConfig and documented in the README and on the docs site, but no key listener existed anywhere in the library: the close button was the only way out of a focus-trapped role=dialog, which left keyboard and screen-reader users stuck and forced every consumer to wire their own listener inside the projected component. The window now rejects its result promise with PortalDismissReasons.ESC, the reason the library exported without ever emitting it, honours keyboard: false per portal, steps aside when another handler has already consumed the key, and reacts only in the window holding focus, so a stack dismisses one dialog at a time from the top.

fixed: scrollable was declared a string on the portal window while HubPortalOptions declares it a boolean. Nothing misbehaved, because the template only tests the input for truthiness and the option reaches it through the name-based setInput, which no compiler ever checks, so the two had been free to disagree since the input was written. They now agree, which is what stops the next reader from believing the window and passing a string.

deprecated: HubPortalModule is deprecated and will be removed in 23.0.0. Its whole body is providers: [HubPortal], and HubPortal is providedIn: root, so importing the module never enabled the service; it only added a redundant second instance in whichever injector declared the import, delegating to the same root HubPortalStack and HubPortalConfig. Inject HubPortal and drop the import. The class carried no @deprecated tag until now, so neither an editor nor the build could warn anyone it was on its way out. See BREAKING_CHANGES.md.

Version 22.0.5 - 9/1/26, 12:00 AM

changed: The homepage in the manifest points at this library's own documentation page rather than at the site root. It is the link a registry shows beside the package and the one a reader clicks from it, and landing on a front page they then have to search is a worse answer than landing on the reference for the package they were already looking at. Metadata only — no code, no types, no styles change, and nothing a consumer imports is affected.

Version 22.0.4 - 8/8/26, 12:00 AM

fixed: Documentation links now point at the canonical localized URLs. The README linked to https://hubui.dev/<path> with no locale prefix and no trailing slash, and both forms are 301-redirected, so every reader arriving from npm or GitHub landed on a redirect instead of the canonical page.

Version 22.0.3 - 7/28/26, 12:00 AM

fixed: Removed the invalid aria-portal attribute from the portal window host (not a real ARIA attribute); role=dialog and the aria-labelledby/describedby wiring stay.

Version 22.0.2 - 7/26/26, 12:00 AM

fixed: Declared the real ng-hub-ui-utils peer range (>=22.0.0); the previous >=1.0.0 floor allowed resolving an incompatible utils major.

Version 22.0.1 - 6/26/26, 12:00 AM

fixed: Corrected the Angular peer dependency range to >=18.0.0. The library uses APIs introduced in Angular 17 (signal input()/output(), the @if control flow and signal queries), so the previous >=16.0.0 range let it install on versions it cannot run on.

fixed: Corrected the ng-hub-ui-utils peer range. The previous caret range resolved to >=1 <2, which excluded the current utils (22.x) and made the peer impossible to satisfy.

Version 22.0.0 - 6/17/26, 12:00 AM

changed: Aligned with Angular 22.

changed: README documentation standardized.

Version 0.3.4 - 6/14/26, 12:00 AM

fixed: Portal window options (animation, windowClass, portalDialogClass, …) are applied through ComponentRef.setInput instead of being assigned onto the instance. Since HubPortalWindow declares them as signal inputs, the direct assignment replaced the read-only signal function and threw TypeError: ctx.animation is not a function on every open().

fixed: Guarded parentNode when removing the window element during teardown, which threw when the element had already been detached.

よくある質問

ng-hub-ui-portal は何に使うものですか?

コンポーネント、TemplateRef、あるいは単なる文字列を、コードから DOM の別の場所にマウントするためのものです。HubPortal を注入して open() を呼ぶと HubPortalRef が返り、その result は Promise です。close() に渡した値で解決し、dismiss() に渡した理由で reject します。closed・dismissed・shown・hidden が observable 版です。コンテンツの内部では HubActivePortal を注入すると、開いた側への参照を持たずに自分自身を閉じられます。

コンテンツをマウントする場所を指定するには?

container オプションが CSS セレクター文字列か HTMLElement を取り、既定は document.body です。解決されるのは open() を呼んだ瞬間の一度きりなので、対象はその時点で document に存在していなければなりません。セレクターが何にも一致しなければ、待たずに例外を投げます。コンテンツはそのコンテナの末尾に追加され、自分のスタイリングのフックは windowClass・portalDialogClass・portalContentClass に差し込みます。

portal と ng-hub-ui-modal は何が違いますか?

ng-hub-ui-modal はダイアログそのもので、バックドロップ、重なり、サイズ、配置まで決まっています。portal はその方針を持たないマウント機構です。バックドロップを描かず、位置も指定しないので、見た目は自分で書くことになります。ただしコンテンツを role=dialog を持つ自前のウィンドウで包むことはしますし、keyboard オプションによる Escape、オープン時のフォーカスとクローズ時の復元、ガードとしての beforeDismiss、支援技術からページの残りを隠す処理は引き続き面倒を見ます。

Angular CDK のようにテンプレートで宣言的に使えますか?

使えません。ディレクティブもアウトレットもなく、公開されているのは HubPortal サービスとその ref、そしてオプションオブジェクトだけです。天秤にかけておきたい帰結が 2 つあります。入力は setInput ではなく componentInstance への代入で設定されるため、シグナル入力で作られたコンポーネントは期待どおりの経路で値を受け取りません。そして open() は即座に document を参照するのでブラウザー向けです。サーバーサイドレンダリングは未検証で、呼び出しには自前のガードが必要です。