addEventListener - greydragon888/real-router GitHub Wiki

getPluginApi().addEventListener

1. Overview

  • What it does: Registers a callback function to listen for router events. Returns an unsubscribe function to remove the listener. Available exclusively through the Plugin API.
  • When to use:
    • For subscribing to router lifecycle events (ROUTER_START, ROUTER_STOP)
    • For subscribing to transition events (TRANSITION_START, TRANSITION_LEAVE_APPROVE, TRANSITION_SUCCESS, TRANSITION_ERROR, TRANSITION_CANCEL)
    • In plugins for tracking router state
    • For integration with logging and analytics systems

2. Signature

import { events } from "@real-router/core";
import { getPluginApi } from "@real-router/core/api";
import type { PluginApi } from "@real-router/core/api";

const pluginApi: PluginApi = getPluginApi(router);

const unsub = pluginApi.addEventListener(eventName, cb);

Full type signature:

addEventListener<E extends EventName>(
  eventName: E,
  cb: Plugin[EventMethodMap[E]]
): Unsubscribe

Event Types and Their Callback Signatures

Event Constant Event Name Plugin Method Callback Signature
events.ROUTER_START "$start" onStart () => void
events.ROUTER_STOP "$stop" onStop () => void
events.TRANSITION_START "$$start" onTransitionStart (toState: State, fromState?: State) => void
events.TRANSITION_LEAVE_APPROVE "$$leaveApprove" onTransitionLeaveApprove (toState: State, fromState?: State) => void
events.TRANSITION_CANCEL "$$cancel" onTransitionCancel (toState: State, fromState?: State) => void
events.TRANSITION_SUCCESS "$$success" onTransitionSuccess (toState: State, fromState: State | undefined, opts: NavigationOptions) => void
events.TRANSITION_ERROR "$$error" onTransitionError (toState: State | undefined, fromState: State | undefined, err: RouterError) => void

Usage Examples

import { events } from "@real-router/core";
import { getPluginApi } from "@real-router/core/api";

const pluginApi = getPluginApi(router);

// Subscribe to successful transitions
const unsub = pluginApi.addEventListener(
  events.TRANSITION_SUCCESS,
  (toState, fromState, opts) => {
    analytics.trackPageView(toState.path);
  },
);

// Later — unsubscribe
unsub();
// Subscribe to router start
const unsub = pluginApi.addEventListener(events.ROUTER_START, () => {
  console.log("Router started");
});
// One callback for different events (allowed)
const transitionLogger = (toState, fromState) =>
  console.log("Transition:", toState?.name);

pluginApi.addEventListener(events.TRANSITION_START, transitionLogger);
pluginApi.addEventListener(events.TRANSITION_SUCCESS, transitionLogger);

3. Parameters

eventName (required)

  • Type: EventName
  • Purpose: Event type to subscribe to
  • Allowed values (use the events constant from @real-router/core):
    • events.ROUTER_START — emitted when router.start() succeeds
    • events.ROUTER_STOP — emitted when router.stop() is called
    • events.TRANSITION_START — emitted when navigation begins
    • events.TRANSITION_LEAVE_APPROVE — emitted after canDeactivate guards pass (before canActivate guards run)
    • events.TRANSITION_SUCCESS — emitted when navigation completes successfully
    • events.TRANSITION_ERROR — emitted when navigation fails
    • events.TRANSITION_CANCEL — emitted when navigation is cancelled
  • Error behavior: Error if the event name is not one of the values above

cb (required)

  • Type: Plugin[EventMethodMap[E]] — callback function with signature matching the event type
  • Purpose: Function called when the event occurs
  • Error behavior: TypeError if not a function

4. Return Value

  • Type: Unsubscribe (() => void)
  • Purpose: Function to remove the listener
  • Behavior:
    • Calling it removes the callback from the event
    • Safe to call multiple times (subsequent calls are no-ops)
    • After unsubscribe, the callback stops receiving events
const unsub = pluginApi.addEventListener(events.ROUTER_START, cb);
unsub(); // Removes the listener
unsub(); // Safe — nothing happens
unsub(); // Safe

5. Possible Errors

Condition Error Type Message
Router is disposed RouterError DISPOSED
Invalid event name Error Invalid event name: {eventName}
Callback is not a function TypeError Expected callback to be a function for event {eventName}

Validation order: disposed check runs first, then event name, then callback type.

6. Related Methods

Method When to use
subscribe() Simplified subscription to TRANSITION_SUCCESS only
usePlugin() Register a plugin object with multiple event hooks

7. Behavior

Registration and Unsubscription

import { events } from "@real-router/core";
import { getPluginApi } from "@real-router/core/api";

const pluginApi = getPluginApi(router);

// Register a listener
let called = false;
const unsub = pluginApi.addEventListener(events.TRANSITION_SUCCESS, () => {
  called = true;
});

await router.navigate("users");
// called === true

// Unsubscribe
unsub();

Works Before router.start()

Listeners can be registered before starting the router:

const pluginApi = getPluginApi(router);

pluginApi.addEventListener(events.ROUTER_START, () => {
  console.log("Router started");
});

await router.start("/home");
// Listener is called during start

Throws After dispose()

router.dispose();

const pluginApi = getPluginApi(router);

pluginApi.addEventListener(events.TRANSITION_SUCCESS, () => {});
// Throws RouterError with code DISPOSED

Listeners Are Called Synchronously in Registration Order

const calls = [];
pluginApi.addEventListener(events.ROUTER_START, () => calls.push(1));
pluginApi.addEventListener(events.ROUTER_START, () => calls.push(2));
pluginApi.addEventListener(events.ROUTER_START, () => calls.push(3));

await router.start("/home");
// calls === [1, 2, 3]

Error Isolation

An error in one callback does not interrupt execution of others:

pluginApi.addEventListener(events.ROUTER_START, () => {
  throw new Error("Boom!");
});
pluginApi.addEventListener(events.ROUTER_START, cb2);
pluginApi.addEventListener(events.ROUTER_START, cb3);

await router.start("/home");
// console.error: "Error in listener..."
// cb2 and cb3 are still called

Guarantees

  • Callback is called synchronously in registration order
  • Error in one callback does not interrupt other listeners
  • Listener list is cloned before iteration (safe against modification during dispatch)
  • Unsubscribe function is safe for multiple calls

8. Migration

API Change

The addEventListener method has moved from the router instance to the Plugin API:

Before:

import { events } from "@real-router/core";

const unsub = router.addEventListener(events.TRANSITION_SUCCESS, (toState) => {
  console.log(toState.name);
});

After:

import { events } from "@real-router/core";
import { getPluginApi } from "@real-router/core/api";

const pluginApi = getPluginApi(router);
const unsub = pluginApi.addEventListener(
  events.TRANSITION_SUCCESS,
  (toState) => {
    console.log(toState.name);
  },
);

Migration Checklist

  • Replace all router.addEventListener(...) calls with getPluginApi(router).addEventListener(...)
  • Import getPluginApi from @real-router/core
  • Use event constants from events (not string literals)
  • Handle RouterError (code DISPOSED) if the router may be disposed when registering listeners