Skip to content

Commit f29243f

Browse files
arturovtatscott
authored andcommitted
fix(core): include signal and component name in NG0600 error message
The signals primitives already pass the written signal node to the error handler, but core ignored it. In dev mode the message now names the signal (when it has a debugName) and the component whose template was rendering. Production output is unchanged.
1 parent 879f54c commit f29243f

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

‎packages/core/src/application/application_ref.ts‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {ComponentFactory} from '../render3/component_ref';
3838
import {getComponentDef, isStandalone} from '../render3/def_getters';
3939
import type {Binding, DirectiveWithBindings} from '../render3/dynamic_bindings';
4040
import {ChangeDetectionMode, detectChangesInternal} from '../render3/instructions/change_detection';
41+
import {getDeclarationComponentDef} from '../render3/instructions/element_validation';
4142
import {profiler} from '../render3/profiler';
4243
import {isReactiveLViewConsumer} from '../render3/reactive_lview_consumer';
4344
import {EffectScheduler} from '../render3/reactivity/root_effect_scheduler';
@@ -71,14 +72,24 @@ export function publishDefaultGlobalUtils() {
7172
* Sets the error for an invalid write to a signal to be an Angular `RuntimeError`.
7273
*/
7374
export function publishSignalConfiguration(): void {
74-
setThrowInvalidWriteToSignalError(() => {
75+
setThrowInvalidWriteToSignalError((node) => {
7576
let errorMessage = '';
7677
if (ngDevMode) {
7778
const activeConsumer = getActiveConsumer();
78-
errorMessage =
79-
activeConsumer && isReactiveLViewConsumer(activeConsumer)
80-
? 'Writing to signals is not allowed while Angular renders the template (eg. interpolations)'
81-
: 'Writing to signals is not allowed in a `computed`';
79+
if (activeConsumer && isReactiveLViewConsumer(activeConsumer)) {
80+
errorMessage =
81+
'Writing to signals is not allowed while Angular renders the template (eg. interpolations)';
82+
const componentName =
83+
activeConsumer.lView && getDeclarationComponentDef(activeConsumer.lView)?.type?.name;
84+
if (componentName) {
85+
errorMessage += `. Template location: '${componentName}' component`;
86+
}
87+
} else {
88+
errorMessage = 'Writing to signals is not allowed in a `computed`';
89+
}
90+
if (node.debugName) {
91+
errorMessage += `. Signal: '${node.debugName}'`;
92+
}
8293
}
8394
throw new RuntimeError(RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT, errorMessage);
8495
});

‎packages/core/test/render3/reactivity_spec.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,59 @@ describe('reactivity', () => {
865865
expect(() => fixture.detectChanges()).toThrowError(/NG0600.*template/);
866866
});
867867

868+
it('should name the component and the signal when writing within a template', async () => {
869+
@Component({
870+
selector: 'write-in-template',
871+
template: '@if (true) {{{write()}}}',
872+
})
873+
class WriteInTemplate {
874+
sig = signal(0, {debugName: 'count'});
875+
write() {
876+
this.sig.set(1);
877+
}
878+
}
879+
880+
const fixture = TestBed.createComponent(WriteInTemplate);
881+
882+
await expectAsync(fixture.whenStable()).toBeRejectedWithError(
883+
/^NG0600: Writing to signals is not allowed while Angular renders the template \(eg\. interpolations\)\. Template location: 'WriteInTemplate' component\. Signal: 'count'\. Find more at/,
884+
);
885+
});
886+
887+
it('should name the signal when writing within computed', async () => {
888+
@Component({
889+
selector: 'write-in-computed',
890+
template: '{{comp()}}',
891+
})
892+
class WriteInComputed {
893+
sig = signal(0, {debugName: 'count'});
894+
comp = computed(() => this.sig.set(1));
895+
}
896+
897+
const fixture = TestBed.createComponent(WriteInComputed);
898+
899+
await expectAsync(fixture.whenStable()).toBeRejectedWithError(
900+
/^NG0600: Writing to signals is not allowed in a `computed`\. Signal: 'count'\. Find more at/,
901+
);
902+
});
903+
904+
it('should keep the message short when the signal has no debugName', async () => {
905+
@Component({
906+
selector: 'write-in-computed',
907+
template: '{{comp()}}',
908+
})
909+
class WriteInComputed {
910+
sig = signal(0);
911+
comp = computed(() => this.sig.set(1));
912+
}
913+
914+
const fixture = TestBed.createComponent(WriteInComputed);
915+
916+
await expectAsync(fixture.whenStable()).toBeRejectedWithError(
917+
/^NG0600: Writing to signals is not allowed in a `computed`\. Find more at/,
918+
);
919+
});
920+
868921
describe('effects created in components should first run after ngOnInit', () => {
869922
it('when created during bootstrapping', () => {
870923
let log: string[] = [];

0 commit comments

Comments
 (0)