Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/src/__tests__/forms-panels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,15 @@ describe('FormsFields inline editing', () => {
const edit = button(fixture, 'Edit email');
expect(edit.disabled).toBe(true);
expect(edit.getAttribute('aria-describedby')).toBe('forms-writes-off');
const notes = [
...(fixture.nativeElement as HTMLElement).querySelectorAll('[id="forms-writes-off"]'),
];
expect(notes.length).toBeGreaterThan(0);
for (const note of notes) {
expect(note.textContent!.trim()).not.toBe('');
expect(note.textContent).not.toContain('function');
expect(note.textContent).not.toContain('[Input Signal');
}
});
});

Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/forms-action-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import { FORMS_STYLES } from './forms-types';
</button>
</div>
@if (!canWrite()) {
<p id="forms-writes-off" class="muted">{{ writesOff }}</p>
<p id="forms-writes-off" class="muted">{{ writesOff() }}</p>
}
<p class="status" role="status">{{ message() }}</p>
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/content/api/angular-debug-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ A required function is one every supported Angular version publishes, so the ove
| `ɵgetInjectorMetadata` | 17.0 | No | Type (`element`, `environment` or `null`) and source of an injector. |
| `ɵsetProfiler` | See note | No | Adds a profiler. From 20.0 it hands back its remover; before that it has one slot (`ng-capabilities.ts` checks the version). |
| `ɵgetSignalGraph` | 19.1 | No | The reactive graph of an injector. Node ids from 20.1. |
| `toggleWatchSignal` | See note | No | Watches or unwatches a signal-graph node by id. Present in 22.3 prereleases; not called by the overlay. |
| `ɵgetDeferBlocks` | 19.1 | No | `@defer` blocks under a node. Replaced by `ɵgetControlFlowBlocks` in 22.0. |
| `ɵgetControlFlowBlocks` | 22.0 | No | `@defer` and `@for` blocks under a node. |
| `ɵgetTransferState` | See note | No | The TransferState entries without Angular's own hydration keys. Present in 22.1; not checked on older versions. |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/content/inspectors/signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Click an entry under **Dependencies (producers)** or **Consumers** to open that

### Resources

Angular builds each `resource()`, `httpResource()` and `rxResource()` from several internal signals, labeled `Resource#name.value`, `Resource#name.state` and so on. The tab folds them into one card under **Resources**, and leaves them out of the **Signals** list. Angular labels these signals from 21.0.1. On earlier versions they have no names, so the tab shows no **Resources** cards and lists them as unnamed nodes. Each card shows:
Angular builds each `resource()`, `httpResource()` and `rxResource()` from several internal signals, labeled `Resource#name.value`, `Resource#name.state` and so on. The tab folds them into one card under **Resources**, and leaves them out of the **Signals** list. Angular labels these signals from 21.0.1, and the 22.3 prereleases add a `Resource#name.params` computed that the tab folds in the same way. On earlier versions they have no names, so the tab shows no **Resources** cards and lists them as unnamed nodes. Each card shows:

| Field | Meaning |
| --------------- | ----------------------------------------------------------------------------------------- |
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion extension/ui/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/devtools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN
- Turning **Counts launcher** on or off in a popped-out panel changes the launcher on the app page that opened it. The popup takes the change only from the window it popped the panel out into.
- The **Pipes** list is one Tab stop with arrow keys, Home and End, like the other lists in the panel, and in every list the Tab stop follows a row selected with a click that does not focus it, as in Safari (#370).
- The popup's **No devtools server found** state and the panel's **Can't reach the devtools server** message link to the troubleshooting docs (#373).
- On Angular 22.3 prereleases the Signals tab folds the new `Resource#name.params` computed into its resource, instead of showing it as an extra **resource 2** card.
- The Forms tab's note under the action buttons, shown when form writes are off, says why instead of printing `[Input Signal: …]`.

### Docs

Expand Down
3 changes: 2 additions & 1 deletion packages/devtools/src/__tests__/debug-api-real.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { STANDALONE, angularAtLeast, zonelessProviders } from './angular-version
class DebugRoot {}
Component({ ...STANDALONE, selector: 'app-root', template: '<p>ok</p>' })(DebugRoot);

describe('NgDebugApi against a real app', () => {
// `bootstrapApplication` and standalone components arrive in 14.0.
describe.runIf(angularAtLeast('14.0'))('NgDebugApi against a real app', () => {
let app: ApplicationRef | null = null;
afterEach(() => {
app?.destroy();
Expand Down
31 changes: 31 additions & 0 deletions packages/devtools/src/__tests__/signal-resources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,37 @@ describe('groupResources', () => {
]);
});

it('folds the params computed of Angular 22.3 prereleases into its resource, and keeps another resource it reads apart', () => {
// From 22.3 `extRequest` reads a `params` computed, which runs the user's params function.
const nodes = [
node('1', 'computed', 'Resource.value', 'Ada'),
node('2', 'linkedSignal', 'Resource.state'),
node('3', 'linkedSignal', 'Resource.extRequest'),
node('4', 'computed', 'Resource.params', 1),
node('5', 'computed', 'Resource.value', ['Ada post']),
node('6', 'linkedSignal', 'Resource.state'),
node('7', 'linkedSignal', 'Resource.extRequest'),
node('8', 'computed', 'Resource.params', 'Ada'),
node('9', 'signal', 'userId', 1),
];
const edges: SignalGraphEdge[] = [
{ consumer: 0, producer: 1 },
{ consumer: 1, producer: 2 },
{ consumer: 2, producer: 3 },
{ consumer: 3, producer: 8 },
{ consumer: 4, producer: 5 },
{ consumer: 5, producer: 6 },
{ consumer: 6, producer: 7 },
// The second resource's params read the first resource's value.
{ consumer: 7, producer: 0 },
];
const resources = groupResources(nodes, edges, [], serialize);
expect(resources.map((r) => [r.name, r.value, r.nodeIds])).toEqual([
['resource 1', 'Ada', ['1', '2', '3', '4']],
['resource 2', ['Ada post'], ['5', '6', '7', '8']],
]);
});

it('derives status from the graph when no instance holds the resource', () => {
const nodes = [
node('1', 'computed', 'Resource#trips.value', '(threw an error)'),
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools/src/debug-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export interface NgDebugApi {
): unknown;
/** The reactive graph of an injector. Node ids from 20.1. @since 19.1 */
ɵgetSignalGraph?(injector: unknown): NgDebugSignalGraph | null;
/** Watches or unwatches a signal-graph node by id. Present in 22.3 prereleases; not called by the overlay. */
toggleWatchSignal?(id: string): void;
/** `@defer` blocks under a node. Replaced by `ɵgetControlFlowBlocks` in 22.0. @since 19.1 */
ɵgetDeferBlocks?(node: Node): unknown[];
/** `@defer` and `@for` blocks under a node. @since 22.0 */
Expand Down Expand Up @@ -156,6 +158,7 @@ export const NG_DEBUG_FUNCTIONS = [
'ɵgetInjectorMetadata',
'ɵsetProfiler',
'ɵgetSignalGraph',
'toggleWatchSignal',
'ɵgetDeferBlocks',
'ɵgetControlFlowBlocks',
'ɵgetTransferState',
Expand Down
14 changes: 12 additions & 2 deletions packages/devtools/src/signal-resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ function find(parent: number[], i: number): number {
return i;
}

/**
* Whether an edge between two parts of same-named resources is internal to one
* resource. The parts that run the user's params function may read another
* resource: `extRequest` up to 22.2, and the `params` computed that the 22.3
* prereleases add between them (`extRequest` then reads only its own `params`).
*/
function ownEdge(consumer: string, producer: string): boolean {
if (consumer === 'extRequest') return producer === 'params';
return consumer !== 'params';
}

function groupsOf(nodes: SignalGraphNode[], edges: SignalGraphEdge[]): Group[] {
const parsed = nodes.map((n) => parseResourceLabel(n.label));
const parent = nodes.map((_, i) => i);
Expand All @@ -115,8 +126,7 @@ function groupsOf(nodes: SignalGraphNode[], edges: SignalGraphEdge[]): Group[] {
if (!valid(e)) continue;
const a = parsed[e.consumer];
const b = parsed[e.producer];
// extRequest reads the user's params, which may read another resource.
if (a && b && a.name === b.name && a.part !== 'extRequest') union(e.consumer, e.producer);
if (a && b && a.name === b.name && ownEdge(a.part, b.part)) union(e.consumer, e.producer);
}
// httpResource signals have plain names: `_statusCode` and friends read the
// resource's own signals, and its `stream` is read by the resource value.
Expand Down
Loading