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

React
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:

Angular
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 = "";  }}
Angular
<!-- 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 useImperativeHandle returns. In Angular, the parent can reach every public member, so use private deliberately.
  • Reach for imperative APIs sparingly in both frameworks. If something can be expressed as a prop/input (e.g. isOpen), prefer that.
  • standalone: true is the default since Angular v19, so it's omitted here.

Quick reference

ReactAngular
useImperativeHandle(ref, () => ({ focus }))Public focus() method
ref.current?.focus()inputBox.focus() / this.inputBox().focus()
Internal useRefprivate field