From the browser console of an Angular app in development mode, how do you read and change a component's state with ng.getComponent and ng.applyChanges?
answer
- select the host element first
- the window.ng debug global
- plain field versus signal
- force a synchronous check
basics
~20 sIn development mode Angular publishes window.ng. Select the component's host element, call ng.getComponent($0) to get its instance, and change a field. A signal's set() schedules an update by itself; a plain field needs ng.applyChanges(instance) to mark it for check and run change detection.
solid answer
~40 sDevelopment builds publish debugging functions on the global `ng`. Pick the component's **host element** in the Elements panel (the browser exposes it as `$0`) and call `ng.getComponent($0)`; it returns the instance or `null` if that element is not a component host. For any element inside a template, `ng.getOwningComponent($0)` returns the component whose view contains it, and `ng.getContext($0)` returns the embedded view context inside `@for`/`@if` blocks. You can then read or change state. If the template reads a **signal**, calling `.set()` notifies Angular and the view updates on its own. A **plain field** assignment from the console is invisible to Angular, so call `ng.applyChanges(instance)`: it marks the component for check (which matters for OnPush, the v22 default) and synchronously runs change detection. `ng.getInjector($0).get(SomeService)` reaches services. None of this exists in optimized builds.
code
ts · 11 lines// In the browser console of an app served with `ng serve`
// 1. Select <app-cart> in the Elements panel ($0 is the selection)
const cart = ng.getComponent($0);
cart.items(); // read a signal
cart.items.set([]); // signal write: the view updates by itself
cart.note = 'debug'; // plain field: Angular is not notified
ng.applyChanges(cart); // mark for check + synchronous change detection
ng.getOwningComponent($0.querySelector('button')); // component whose view holds the buttongo deeper
Remember the routine: select the host element, call ng.getComponent($0), change state, and call ng.applyChanges when the screen does not update.
Explain why a plain field change is invisible (zoneless default, OnPush default, console code outside any zone) while a signal write updates the view by itself.
Use getOwningComponent, getContext, getInjector and getListeners to confirm state, providers and event wiring in a live dev session before touching code.
Build a team debugging practice around dev-mode tooling and decide where a non-optimized diagnostic environment is justified.
## The `ng` global When an Angular application runs in **development mode**, the framework publishes a set of debugging functions on `window.ng`. They exist for exactly one audience: a developer at the browser console. Production builds define `ngDevMode` as `false`, and the whole mechanism is compiled out, so `ng` is `undefined` there. The public functions include: | Function | Returns | | :-- | :-- | | `ng.getComponent(el)` | The component instance whose **host** is `el`, or `null` | | `ng.getOwningComponent(elOrDir)` | The component whose **view contains** the element | | `ng.getContext(el)` | The embedded-view context around `el` (inside `@for`, `@if`, `*ngFor`...), else the owning component | | `ng.getDirectives(node)` | Directive instances on the node | | `ng.getInjector(elOrDir)` | The injector at that element or directive | | `ng.getListeners(el)` | Event listeners Angular registered on the element | | `ng.getHostElement(instance)` | The host element of a component or directive | | `ng.getRootComponents(elOrDir)` | The root components of the application | | `ng.applyChanges(instance)` | Marks the component for check and runs change detection synchronously | Functions whose names start with `ɵ` are internal and are meant for tools like Angular DevTools, not for you. ## The inspect-and-modify routine 1. In the Elements panel, select the component's host element, for example `<app-cart>`. The browser exposes the selection as `$0`. 2. Get the instance: `const cart = ng.getComponent($0);` 3. Read state: `cart.items()` for a signal, `cart.discount` for a plain field. 4. Change state and let the view catch up (see below). 5. Reach a service: the easiest route is a field the component already holds (`cart.cartService`); `ng.getInjector($0)` returns the element's injector when you have a token reference to look it up with. A common stumble: selecting a `<div>` inside the component and calling `getComponent` returns `null`, because the `div` hosts no component. Use `getOwningComponent($0)` instead, or select the component's own tag. ## Why a plain field change does not show up Angular only re-renders a view when change detection runs and the view is eligible to be checked. - Since Angular 21 applications are **zoneless** by default: nothing patches browser APIs, and change detection is scheduled by notifications such as signal writes, template events, or `markForCheck()`. An assignment typed into the console notifies nobody. - Since Angular 22 components default to **OnPush**, so even when change detection runs, a component that was not marked dirty is skipped. - Even in an older zone-based app, code run from the console does not run inside Angular's zone, so it triggers nothing. `ng.applyChanges(cart)` solves all three: it marks the component's view dirty (the equivalent of `markForCheck`) and then synchronously runs change detection from the application's root components. ## Why a signal change does show up If the template reads `items()`, then `cart.items.set([...])` marks the views that read that signal for refresh and notifies Angular's scheduler, so the screen updates without `applyChanges`. This is a useful live demonstration of why signals fit zoneless, OnPush-by-default Angular. ## Other questions the console answers - **Which directives are on this element?** `ng.getDirectives($0)` lists them, which settles "is my directive even applied?" in one call. - **Is my `(click)` handler wired?** `ng.getListeners($0)` lists the listeners Angular registered, with their event names. - **Which item is this row?** Inside an `@for` block, `ng.getContext($0)` returns the embedded view's context, including the current item as `$implicit`. - **Which app does this belong to?** `ng.getRootComponents($0)` returns the root components, useful on pages with several Angular roots. ## Practical cautions - **Development only**: none of these functions exist in an optimized build. Code that must work in production cannot use them. - **Not an API for app code**: they are debugging aids; application code should use `ChangeDetectorRef`, signals and DI instead. - **Side effects are real**: calling methods from the console runs the app's own logic, including HTTP calls. - **Use Angular DevTools alongside**: its `$ng0` shortcut gives you the selected instance without hunting for the host element.
- Why does ng.getComponent($0) return null when a plain div inside the component is selected?`getComponent` returns the component *hosted* on that element, and a `div` hosts none. Use `ng.getOwningComponent($0)` to get the component whose view contains the element, or `ng.getContext($0)` to get the surrounding embedded-view context, such as the current item inside an `@for` block.
- Why is window.ng undefined on an Angular production build?Angular publishes the debug utilities only when `ngDevMode` is on. When the CLI optimizes scripts it defines `ngDevMode` as `false`, and the minifier removes the publishing code together with other dev-only checks. To use the utilities against a deployed environment, it must be built with optimization disabled.
saying these in an interview costs you the question
- Assigning a plain field from the console re-renders the component automatically.
- ng.getComponent works on any element inside the component's template.
- window.ng is available in production for support staff to debug users' sessions.
- ng.applyChanges only checks the given component and never its parents.
- Application code should call ng.applyChanges instead of using ChangeDetectorRef.