Skip to content

Conditional Visibility

since v1.4.0

ngDiagram has built-in support for hidden content. This page explains the three tools you have, when to use each of them, and why plain CSS is not one of them.

Nodes and edges have an optional hidden flag. Set hidden: true on a node or an edge in the model, and it disappears from the diagram. The element stays in the model and in the DOM (as display: none), keeps its size and position, and comes back when you set the flag to false. This is the main tool. Use it for expand/collapse, filtering, and any other visibility state that belongs to your data.

The ngDiagramHidden directive hides a node or edge from inside its template. Use it when visibility is a UI state that you do not want to store in the model, for example a local collapsed signal in your node component. The effect is the same as with the flag.

Ports and edge labels have a hidden input. They are declared in templates, not in the model, so they have no flag. Bind [hidden] on ng-diagram-port or ng-diagram-base-edge-label to hide them independently of their node or edge, for example ports that are visible only in an edit mode.

Why not plain CSS display: none? The library measures every element after it renders, and it waits for these measurements before the diagram is ready. It cannot tell an element hidden with CSS from an element that is not measured yet, so it keeps waiting, and every load ends only after the measurement timeout (2 s). The flag, the directive and the input tell the library that the element is hidden on purpose. The library then skips it at initialization, leaves it out of user interactions such as hit-testing and zoomToFit, and measures it again when it becomes visible.

The sections below show each tool with an example. Choosing a pattern compares them side by side.

Hiding nodes and edges with the hidden flag

Section titled “Hiding nodes and edges with the hidden flag”

Set hidden in the model, or toggle it at runtime:

// Collapse a group: keep the group visible and hide only its children.
// (Hiding the group itself would hide all of its descendants automatically.)
await this.modelService.updateNodes(childIds.map((id) => ({ id, hidden: true })));
// Expand
await this.modelService.updateNodes(childIds.map((id) => ({ id, hidden: false })));

Hiding a group hides all of its descendants. Hiding a node hides every edge connected to it, so you do not need to update the edges yourself.

Hiding nodes and edges with the ngDiagramHidden directive

Section titled “Hiding nodes and edges with the ngDiagramHidden directive”

Use the NgDiagramHiddenDirective inside a node or edge template when visibility is a UI state that you do not want to store in the model:

<!-- inside a custom node template -->
<div class="my-node" [ngDiagramHidden]="collapsed()">
<!-- content -->
</div>

An element is hidden when any of these applies: the model flag, the template binding, a hidden ancestor group, or (for edges) a hidden endpoint node. All features read the same effective visibility.

To hide many elements at once, prefer the model hidden flag. One updateNodes call can hide any number of elements in one pass. Template bindings work per element. Bindings that change in the same change detection cycle are processed together, but the model flag is still cheaper, and it also works with virtualization.

Hiding ports and edge labels with the hidden input

Section titled “Hiding ports and edge labels with the hidden input”

Ports and labels are declared in templates, not in the model. To hide them, use the hidden input. It works independently of the node or edge that owns them:

<ng-diagram-port id="out" side="right" type="source" [hidden]="!editMode()" />
<ng-diagram-base-edge-label [id]="labelId" [positionOnEdge]="0.5" [hidden]="!showLabels()">
<!-- label content -->
</ng-diagram-base-edge-label>

A plain hidden attribute without a binding works too. It means hidden, like the native HTML attribute.

A hidden port cannot start or receive a link, and the linking preview does not snap to it. Edges connected to a hidden port stay visible. They keep using the port’s last measured position. If the edge should disappear with the port, hide the edge itself with its hidden flag.

There are three ways to hide content. They behave differently:

PatternElement exists?Measurement-safe?When to use
hidden flag / inputYes, display: none✅ Yes. No init delay, re-measures at onceExpand/collapse, edit modes, filtering: anything that toggles at runtime
@if (removes the element)No✅ Yes. The element is never measuredContent that should not exist at all. Note: a removed port is also removed from measuredPorts, so its edges lose their anchor and connect to the node instead
plain CSS display: noneYes⚠️ NoAvoid. The library cannot tell a hidden element from an element that is not measured yet, so every load waits for the measurement timeout (2 s) before it finishes

The hidden flag and input are the supported way. They tell the library that the element is hidden on purpose. The measurement system can then skip the element at init, and stop waiting for it when it is hidden at runtime.

  • hidden: an optional flag on nodes and edges. You set it. When it is not set, the element is visible.
  • computedHidden: the effective visibility. The library computes it. A node is effectively hidden when its own flag or template binding is set, or when any ancestor group is hidden. An edge is effectively hidden when its own flag or binding is set, or when either endpoint node is effectively hidden. This property is read-only and is not saved with the model.

Effectively hidden elements:

  • stay in the DOM with display: none. They keep their size and position, and are measured again automatically when they become visible;
  • never block initialization or waitForMeasurements;
  • are ignored by all user interactions: hit-testing, selectAll, keyboard move, drag, box selection, linking, zoomToFit bounds, the minimap, virtualization and edge routing. Programmatic APIs such as select, centerOnNode, bringToFront/sendToBack and the group APIs do not skip hidden elements. It is up to you whether to call them on hidden elements;
  • keep their selected flag (hiding does not deselect), but they cannot be moved, and deleteSelection skips them. Deleting a group with deleteSelection or deleteNodes still deletes its hidden children, because both delete all descendants. Deleting a node still removes its hidden edges.

The library never sets an inline display style on visible nodes, edges or labels, so your own CSS keeps full control over them. Ports are the one exception: the port component has always managed its own inline display style. To hide a port, use its hidden input, not CSS.

With virtualization enabled, effectively hidden elements are removed from the DOM instead of being kept with display: none. The result is the same: their geometry, ports and labels stay in the model, and they are measured again when they become visible. There is one limitation: NgDiagramHiddenDirective does not work with virtualization, because hiding the element would remove the template that holds the binding. The directive is then ignored, and a console warning is logged. Use the model hidden flag instead. The hidden inputs on ports and edge labels keep working, because their node or edge stays in the DOM while it is visible.

Copying or cutting nodes also copies their descendants and the edges between the copied nodes. A collapsed group is copied together with its hidden children and their edges. Effectively hidden selected elements are skipped, the same as in deleteSelection. Pasted hidden content is not selected, so a paste never creates a selection that you cannot see.

hidden is part of your data. It is kept by toJSON() and by model initialization. computedHidden is a runtime property and is removed automatically.