Skip to content

Commit 6c2fbad

Browse files
JeanMechepkozlowski-opensource
authored andcommitted
refactor(core): Improve NG0600 error message. (angular#60418)
This commit adds the mention of templates as illegal context to write signals. fixes angular#60143 PR Close angular#60418
1 parent 00dff8b commit 6c2fbad

File tree

3 files changed

+55
-14
lines changed

3 files changed

+55
-14
lines changed

packages/core/src/application/application_ref.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import '../util/ng_hmr_mode';
1010
import '../util/ng_jit_mode';
1111
import '../util/ng_server_mode';
1212

13-
import {setActiveConsumer, setThrowInvalidWriteToSignalError} from '../../primitives/signals';
13+
import {
14+
setActiveConsumer,
15+
getActiveConsumer,
16+
setThrowInvalidWriteToSignalError,
17+
} from '../../primitives/signals';
1418
import {type Observable, Subject, type Subscription} from 'rxjs';
1519
import {map} from 'rxjs/operators';
1620

@@ -44,6 +48,7 @@ import {NgZone} from '../zone/ng_zone';
4448
import {profiler} from '../render3/profiler';
4549
import {ProfilerEvent} from '../render3/profiler_types';
4650
import {EffectScheduler} from '../render3/reactivity/root_effect_scheduler';
51+
import {isReactiveLViewConsumer} from '../render3/reactive_lview_consumer';
4752
import {ApplicationInitStatus} from './application_init';
4853
import {TracingAction, TracingService, TracingSnapshot} from './tracing';
4954

@@ -70,10 +75,15 @@ export function publishDefaultGlobalUtils() {
7075
*/
7176
export function publishSignalConfiguration(): void {
7277
setThrowInvalidWriteToSignalError(() => {
73-
throw new RuntimeError(
74-
RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT,
75-
ngDevMode && 'Writing to signals is not allowed in a `computed`.',
76-
);
78+
let errorMessage = '';
79+
if (ngDevMode) {
80+
const activeConsumer = getActiveConsumer();
81+
errorMessage =
82+
activeConsumer && isReactiveLViewConsumer(activeConsumer)
83+
? 'Writing to signals is not allowed while Angular renders the template (eg. interpolations)'
84+
: 'Writing to signals is not allowed in a `computed`';
85+
}
86+
throw new RuntimeError(RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT, errorMessage);
7787
});
7888
}
7989

packages/core/src/render3/reactive_lview_consumer.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,8 @@
88

99
import {REACTIVE_NODE, ReactiveNode} from '../../primitives/signals';
1010

11-
import {
12-
LView,
13-
PARENT,
14-
REACTIVE_TEMPLATE_CONSUMER,
15-
TVIEW,
16-
TView,
17-
TViewType,
18-
} from './interfaces/view';
11+
import {LView, REACTIVE_TEMPLATE_CONSUMER, TVIEW, TView, TViewType} from './interfaces/view';
1912
import {getLViewParent, markAncestorsForTraversal, markViewForRefresh} from './util/view_utils';
20-
import {assertDefined} from '../util/assert';
2113

2214
let freeConsumers: ReactiveNode[] = [];
2315
export interface ReactiveLViewConsumer extends ReactiveNode {
@@ -117,3 +109,7 @@ export const TEMPORARY_CONSUMER_NODE = {
117109
export function viewShouldHaveReactiveConsumer(tView: TView) {
118110
return tView.type !== TViewType.Embedded;
119111
}
112+
113+
export function isReactiveLViewConsumer(node: ReactiveNode): node is ReactiveLViewConsumer {
114+
return node.kind === 'template';
115+
}

packages/core/test/render3/reactivity_spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,41 @@ describe('reactivity', () => {
751751
expect(effectRef[SIGNAL].debugName).toBe('TEST_DEBUG_NAME');
752752
});
753753

754+
it('should disallow writing to signals within computed', () => {
755+
@Component({
756+
selector: 'with-input',
757+
template: '{{comp()}}',
758+
})
759+
class WriteComputed {
760+
sig = signal(0);
761+
comp = computed(() => {
762+
this.sig.set(this.sig() + 1);
763+
return this.sig();
764+
});
765+
}
766+
767+
const fixture = TestBed.createComponent(WriteComputed);
768+
769+
expect(() => fixture.detectChanges()).toThrowError(/NG0600.*in a `computed`/);
770+
});
771+
772+
it('should disallow writing to signals within a template', () => {
773+
@Component({
774+
selector: 'with-input',
775+
template: '{{func()}}',
776+
})
777+
class WriteComputed {
778+
sig = signal(0);
779+
func() {
780+
this.sig.set(this.sig() + 1);
781+
}
782+
}
783+
784+
const fixture = TestBed.createComponent(WriteComputed);
785+
786+
expect(() => fixture.detectChanges()).toThrowError(/NG0600.*template/);
787+
});
788+
754789
describe('effects created in components should first run after ngOnInit', () => {
755790
it('when created during bootstrapping', () => {
756791
let log: string[] = [];

0 commit comments

Comments
 (0)