Working with Refs & PortalsEntry 4 of 6
Exposing a Child API with useImperativeHandle
A hook that customizes what a parent receives through a ref, replacing the raw DOM node with an object of chosen methods.
Notes
Instead of giving the parent the whole DOM element, the child decides exactly which operations are allowed (e.g. open(), focus(), clear()). This keeps the parent decoupled from the child's internal markup: the child can swap a <div> for a <dialog> without breaking the parent.
useImperativeHandle(ref, createHandle) takes the ref received as a prop and a function returning the exposed object.
What / Why / When
- What
A controlled, minimal imperative API on a child component.
- Why
Encapsulation. The parent calls intent-based methods instead of manipulating DOM it doesn't own.
- When
A parent must trigger an action in a child that isn't naturally expressed as props (open a modal, focus a field, play a video).
- Use case
A parent button that focuses and clears a custom input box.
Code compare
import { useImperativeHandle, useRef, type Ref } from "react";export type InputBoxHandle = { focus: () => void; clear: () => void;};export function InputBox({ ref }: { ref?: Ref<InputBoxHandle> }) { const inputRef = useRef<HTMLInputElement>(null); // private to the child useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus(), clear: () => { if (inputRef.current) inputRef.current.value = ""; }, })); return <input ref={inputRef} placeholder="Type here..." />;}export function App() { const inputBoxRef = useRef<InputBoxHandle>(null); return ( <div> <InputBox ref={inputBoxRef} /> <button onClick={() => inputBoxRef.current?.focus()}>Focus the input</button> <button onClick={() => inputBoxRef.current?.clear()}>Clear</button> </div> );}Public methods on the component class are the imperative API. Keep internals private:
import { Component, ElementRef, viewChild } from "@angular/core";@Component({ selector: "app-input-box", template: `<input #inputEl placeholder="Type here..." />`,})export class InputBoxComponent { private inputEl = viewChild.required<ElementRef<HTMLInputElement>>("inputEl"); focus() { this.inputEl().nativeElement.focus(); } clear() { this.inputEl().nativeElement.value = ""; }}<!-- Parent template: a template variable on a component gives its instance --><app-input-box #inputBox /><button (click)="inputBox.focus()">Focus the input</button><button (click)="inputBox.clear()">Clear</button>Differences and gotchas
- In React, the parent sees only what
useImperativeHandlereturns. In Angular, the parent can reach every public member, so useprivatedeliberately. - Reach for imperative APIs sparingly in both frameworks. If something can be expressed as a prop/input (e.g.
isOpen), prefer that. standalone: trueis the default since Angular v19, so it's omitted here.
Quick reference
| React | Angular |
|---|---|
useImperativeHandle(ref, () => ({ focus })) | Public focus() method |
ref.current?.focus() | inputBox.focus() / this.inputBox().focus() |
Internal useRef | private field |