Skip to content

@protoutil/angular

Angular components and validators for working with AIP-160 filter expressions and protovalidate message validation. Built on Angular signals, Angular Material, and @protoutil/aip.

Terminal window
npm install @protoutil/angular
PackageVersion
@angular/common^21.2.0
@angular/core^21.2.0
@angular/forms^21.2.0
@bufbuild/protobuf^2.11.0
@bufbuild/protovalidate^1.1.1
@protoutil/aip*

The filter tree components also use @angular/cdk (drag-drop) and @angular/material (form fields, buttons, icons, select, etc.).

The <aip-filter-editor> component provides a full-featured UI for building AIP-160 filter expressions. It combines a visual tree editor with both a guided stepper input and a free-text input.

<aip-filter-editor
[declarations]="declarations"
[initialTree]="initialTree"
[initialField]="'status'"
[initialInputMode]="'stepper'"
(treeChange)="onTreeChange($event)"
/>
InputTypeDescription
declarationsDecl[]Field declarations for type-aware operator selection and validation
initialTreeFilterNodeOptional initial tree state
initialFieldstring | nullPre-select a field in the stepper
initialInputMode'stepper' | 'text'Which input mode to show initially (default: 'stepper')
OutputTypeDescription
treeChangeFilterNodeEmitted after every tree mutation

The editor supports two input modes, togglable via icon buttons:

  • Stepper mode: A guided field → operator → value flow. Operators are determined by the field’s CEL type (e.g., strings get contains/startsWith/endsWith, numbers get comparison operators).
  • Text mode: A free-text input with real-time AIP filter validation (debounced, type-checked).

The <aip-filter-tree> component renders a drag-and-drop filter tree with undo/redo support.

<aip-filter-tree
[initialTree]="rootNode"
(treeChange)="onTreeChange($event)"
/>

Features:

  • Drag and drop: Reorder filters and nest them into AND/OR groups using CDK drag-drop
  • Conjunction toggle: Click a branch header to toggle between AND and OR
  • Delete nodes: Remove individual filter conditions or entire groups
  • Undo/Redo: Keyboard shortcuts (Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z) and programmatic API
  • Clear all: Reset the tree to an empty state

The FilterNode data model represents filter expressions as a tree:

import {
createFilterLeafNode,
createFilterBranchNode,
exprToFilterNode,
filterNodeToExpr,
isFilterNode,
isFilterLeafNode,
isFilterBranchNode,
cloneNode,
} from "@protoutil/angular";
  • Leaf nodes hold a single Expr (a filter condition like status = "active")
  • Branch nodes hold child nodes and a conjunction (_&&_ for AND, _||_ for OR)
import { parse, check, unparse } from "@protoutil/aip/filtering";
import { exprToFilterNode, filterNodeToExpr } from "@protoutil/angular";
// Parse a filter string into an Expr, then convert to a FilterNode tree
const { checkedExpr } = check(parse('status = "active" AND rating > 3'));
const tree = exprToFilterNode(checkedExpr.expr!);
// Convert back to an Expr and unparse to a filter string
const expr = filterNodeToExpr(tree);
const filterString = unparse(expr!);

FilterTreeService is an injectable service that provides immutable tree manipulation:

import { FilterTreeService } from "@protoutil/angular";
MethodDescription
applyDrop(root, dragId, position)Apply a drag-and-drop operation
toggleConjunction(root, branchId)Toggle a branch between AND and OR
deleteNode(root, nodeId)Remove a node from the tree
initHistory(root)Create a fresh undo/redo history
commitState(history, newRoot)Push a new state onto the history stack
undo(history)Step back one state
redo(history)Step forward one state
currentRoot(history)Get the tree at the current history position
canUndo(history) / canRedo(history)Check if undo/redo is available

operatorsForType returns the available filter operators for a given CEL type:

import { operatorsForType, valueInputKindForType } from "@protoutil/angular";
const ops = operatorsForType(stringType);
const inputKind = valueInputKindForType(intType); // "number"
CEL TypeAvailable Operators
BOOL=, !=
INT64, UINT64, DOUBLE=, !=, <, <=, >, >=
STRING=, !=, contains, startsWith, endsWith
TIMESTAMP, DURATION=, !=, <, <=, >, >=

An Angular signal forms validator for AIP filter strings:

import { validateAipFilter } from "@protoutil/angular";
const filterForm = form(signal(""), (path) => {
debounce(path, 300);
validateAipFilter(path, () => declarations);
});

Integrates protovalidate with Angular signal forms:

import { validateMessageTree } from "@protoutil/angular";
validateMessageTree(formPath, validator, MyMessageSchema);

All UI labels use Angular’s $localize for i18n support. The following message IDs are available:

IDDefaultUsed In
@@filterOperator.equalsequalsStepper operator select
@@filterOperator.notEqualsnot equalsStepper operator select
@@filterOperator.lessThanless thanStepper operator select
@@filterOperator.lessOrEqualless or equalStepper operator select
@@filterOperator.greaterThangreater thanStepper operator select
@@filterOperator.greaterOrEqualgreater or equalStepper operator select
@@filterOperator.containscontainsStepper operator select
@@filterOperator.startsWithstarts withStepper operator select
@@filterOperator.endsWithends withStepper operator select
@@filterStepper.fieldLabelFieldStepper labels
@@filterStepper.operatorLabelOperatorStepper labels
@@filterStepper.valueLabelValueStepper labels
@@filterStepper.addLabelAddStepper labels
@@filterTextInput.placeholdere.g. status = “ACTIVE” …Text input
@@filterTextInput.labelFilter expressionText input
@@filterTextInput.submitLabelAddText input