Visitar URL original
fix(router): set initial blocking resource input synchronously on act… · angular/angular@d23932f · GitHub
Skip to content

Commit d23932f

Browse files
committed
fix(router): set initial blocking resource input synchronously on activation
When a blocking router resource is bound to a component input via withComponentInputBinding(), createResourceOutletBindingEffects() previously only registered an effect() with componentRef.injector. Because the routed component is instantiated before bindActivatedRouteToOutletComponent() runs, any effect() created in the component constructor (such as Signal Forms' form() field management effect) was registered on the view before the router's binding effect, and lifecycle hooks (ngOnChanges, ngOnInit, ngDoCheck) also ran before view effects on the initial render. Accessing a required input or model in those constructor effects or hooks threw NG0950 / NG0952 on first render. Set the resolved blocking resource value synchronously via componentRef.setInput() upon activation while keeping the effect on componentRef.injector for subsequent reactive updates and error boundary propagation. Fixes #70974 (cherry picked from commit 3c13074)
1 parent 0ecaf8e commit d23932f

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎packages/router/src/router_resource.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
computed,
2222
assertInInjectionContext,
2323
reflectComponentType,
24+
untracked,
2425
} from '@angular/core';
2526
import {Router} from './router';
2627
import type {ActivatedRoute} from './router_state';
@@ -200,6 +201,7 @@ export function createResourceOutletBindingEffects(
200201
continue;
201202
}
202203

204+
componentRef.setInput(templateName, untracked(resource.value));
203205
const effectRef = effect(
204206
() => {
205207
componentRef.setInput(templateName, resource.value());

‎packages/router/test/directives/router_outlet.spec.ts‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@
77
*/
88

99
import {CommonModule, NgForOf} from '@angular/common';
10-
import {Component, inject, Input, Type, NgModule, signal, resource} from '@angular/core';
10+
import {
11+
Component,
12+
effect,
13+
inject,
14+
input,
15+
Input,
16+
model,
17+
NgModule,
18+
resource,
19+
signal,
20+
Type,
21+
} from '@angular/core';
1122
import {ComponentFixture, TestBed} from '@angular/core/testing';
1223
import {
1324
provideRouter as internalProvideRouter,
@@ -652,6 +663,56 @@ describe('component input binding', () => {
652663
await harness.fixture.whenStable();
653664
expect(instance.result).toEqual('data: updated');
654665
});
666+
667+
it('sets blocking resource input synchronously so input.required() is available in ngOnInit and constructor effects on first render', async () => {
668+
const log: string[] = [];
669+
670+
@Component({
671+
template: '{{ passenger() }}',
672+
})
673+
class PassengerEdit {
674+
@Input({isSignal: true, required: true} as any)
675+
readonly passenger = model.required<string>();
676+
@Input({isSignal: true, required: true} as any)
677+
readonly details = input.required<string>();
678+
679+
constructor() {
680+
effect(() => {
681+
log.push(`effect:${this.passenger()}:${this.details()}`);
682+
});
683+
}
684+
685+
ngOnInit() {
686+
log.push(`init:${this.passenger()}:${this.details()}`);
687+
}
688+
}
689+
690+
TestBed.configureTestingModule({
691+
providers: [
692+
provideRouter(
693+
[
694+
{
695+
path: 'edit',
696+
component: PassengerEdit,
697+
resources: () => ({
698+
passenger: resource({loader: async () => 'Alice'}),
699+
details: resource({loader: async () => 'VIP'}),
700+
}),
701+
},
702+
],
703+
withComponentInputBinding(),
704+
withRouterResources(),
705+
),
706+
],
707+
});
708+
709+
const harness = await RouterTestingHarness.create();
710+
await harness.navigateByUrl('/edit', PassengerEdit);
711+
await harness.fixture.whenStable();
712+
713+
expect(log).toEqual(['init:Alice:VIP', 'effect:Alice:VIP']);
714+
expect(harness.routeNativeElement?.textContent).toBe('Alice');
715+
});
655716
});
656717

657718
describe('injectors', () => {

0 commit comments

Comments
 (0)