-
Notifications
8000
You must be signed in to change notification settings - Fork 26.2k
"Input is required but no value is available yet" with toObservable if the content is dynamic #59067
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
A narrowed down repro: https://stackblitz.com/edit/stackblitz-starters-u3sbf6?file=src%2Fmain.ts |
Thank you, that is much more simple. I've tried to reproduce it with Just as a side note, which is somewhat obvious after you narrowed down the example, if we use |
I encountered the same issue with dynamic content in a loop. creatable(); is an signal from my signalStore
Root Cause: Add an *ngIf Condition
|
It can be narrowed even further: @Component({
selector: 'my-app',
template: `<app-child id="5"/>`,
imports: [ChildComponent],
})
export class App {}
@Component({
selector: 'app-child',
template: ``,
})
export class ChildComponent {
id = input.required<string>({ alias: 'id' });
constructor() {
const err = this.id();
}
} |
@e-oz Reading a required input will always fail in the constructor. Your example exhibit an expected behavior. |
because of Why isn't "runEffectsInView" run after "detectChangesInEmbeddedViews"? |
Has anyone been able to come up with a workaround for this issue? Since it hasn't been marked as a bug I'm assuming it won't get fixed any time soon. |
The workaround is simple: private readonly injector = inject(Injector);
protected readonly displayValue$ = defer(() =>
toObservable(this.displayValue, { injector: this.injector }).pipe(
startWith(null)
)
); https://stackblitz.com/edit/stackblitz-starters-aapwxsuc?file=src%2Fmain.ts |
call effect inside AfterContentInit lifecycle hook. |
The only caveat here is that on every subscription a new observable will be created, so be careful when using it. |
@csisy-bt4w as happens with any cold observable. Use |
@e-oz You're somewhat right, even the non-deferred one's pipeline is re-executed if not shared. However, just wanted to point out that with private readonly injector = inject(Injector);
private readonly source = signal(42);
private readonly source$ = defer(() =>
toObservable(this.source, { injector: this.injector }).pipe(
tap((val) => console.log('value', val)),
shareReplay({ bufferSize: 1, refCount: true }), // <-- shareReplay here
)
);
ngOnInit(): void {
this.source$.subscribe((val) => console.log('sub1', val));
this.source$.subscribe((val) => console.log('sub2', val));
} That https://stackblitz.com/edit/stackblitz-starters-7rhpx4gd?file=src%2Fmain.ts |
@csisy-bt4w that's correct! |
This is not a workaround unless you're maybe dealing with static data. It will not work when the data is loaded async. |
I'm not quite sure about the scenario you're referring to. Could you provide a more detailed explanation? I'd be happy to discuss it with you. |
Simple: if you have a select component with options, your workaround works for the retieval of initial options. However, if the options change programatically, after the component was created, the options retrieved in the effect are not 'complete', as mentionned above. They do not have their new [id] inputs for instance (values are undefined, and if the inputs are required, you get the error). One simple workaround I found is to put the code inside the effect in a setTimeout(). I hate this solution, but it seems to work for me. The major problem I have, that is somewhat related to the original problem, is that the behaviour is different between v18 and v19. In v18, @ContentChildren and contentChildren() had the same behavior: this.contentChildren.changes.pipe(startWith(this.contentChildren)).subscribe(value => ....) and retrieving them from the contentChildren() signal like effect(() => {
const contentChildren = this.contentChildren();
....
}) would get me the same values, with the same child components after the were initialized (form what I can guess, after the OnInit hook). In v19, the signal now works differently from the old @ContentChildren notation. The signal gets the child components AFTER they are created but BEFORE they are initialized, so any input (like a static [value] or [id] input) is not available in the contentChildren signal when an effect or computed is triggered. This creates the 'Input required' problem. Is this really by design ? I don't really see the use case of wanting the children as 'empty shells' and NOT initialized. If this is the case, I will migrate back to using @ContentChildren because it seems quite complex to get the child components 'the old way' with the contentChildren signal. |
You can reproduce the problem here : https://stackblitz.com/edit/stackblitz-starters-ww25jcsa?file=src%2Fmain.ts You can see in the console that the option values are undefined when the contentChildren signal is triggered. Edit: I updated the stackblitz to include an 'old way' @ContentChildren and a button to change the children programatically. Edit2: here is the same exact code running with Angular 18.2 |
We had the exact same use case than @JusuVh and the workaround we used to get children options inputs was to move the effect creation inside afterContentInit instead of the constructor. |
As I mentioned above, this works for the initial content creation but if the content changes after that, the signal returns 'empty' child components (see button in stackblitz above). |
If I didn't misunderstand what you guys meant, |
Is there anything news on this ? I'm building / maintaining a component library and this is quite a problematic regression for many group components that have child items with required value inputs, like radios, switchs, selects and so on. To fix the issue, I have to basically wrap all code run in effects is a setTimeout, like so :
I have not found any better way to consistently and easily fix the problem. Should I open a new issue specifically with the examples I showed above ? |
v19 introduced timing changes for |
I see, thank you for the clarification. So you can confirm that this is indeed by design? And what I also find strange is that the behaviour is not consistent between using direct content projection or a Edit: and to repeat what I said earlier, what's the use case of wanting the contentChildren before they are initialized, because they are, as far as I'm concerned, unusable ? |
JeanMeche, could you confirm if this behavior is by design? If yes, can you explain in more detail why Angular doesn’t run effects after detectChangesInEmbeddedViews? before signal-based, we rely on lifecycle hooks ngAfterContentInit and ngAfterViewInit. If Angular were to run effects before detectChangesInEmbeddedViews, we would still need ngAfterContentInit. However, if effects were run after detectChangesInEmbeddedViews, the effect could replace ngAfterContentInit. The afterRenderEffect is used for DOM manipulation, and it is typically used to replace ngAfterViewInit. |
I've have some explainations about the changes in v19 in this article: https://riegler.fr/blog/2024-10-15-effect-context/
|
afterRenderEffect and afterNextRender occur at the same time, right? Before signal-based, we had afterContentInit and QueryList for detecting content changes. We listened for changes not necessarily for DOM manipulation, but perhaps to update state. Therefore, afterRenderEffect isn’t as suitable for that compared to effect. Ideally, we want to use effect to replace afterContentInit/QueryList while maintaining the same timing. |
|
Ok, I played with Maybe I need to sleep on it... I would have prefered something to control the behaviour in the contentChildren() signal itself, like a 'afterRender: true' option, to control when the signal triggers, rather than having to think when the signal should be read. |
JeanMeche, how did you define "after rendering"? look at my above example there are 3 components, App, Parent, Child
App Template why the below code got the error? when we say "after rendering" (e.g., afterNextRender / afterViewChecked), it typically means after the component's LView has been refreshed. In the example above, the effect in the ParentComponent runs when the App's LView is being refreshed, but the Parent and Child LViews have not started refreshing yet. However, if we switch to "afterEffectRender," it would mean that all the Parent and Child LViews have completed their refresh. |
This is more or less the discussion we're having in this issue. This is indeed confusing. |
Hi, we faced this issue with migrating from Angular v18 to v19. This was for us unexpected behavior. I can agree with the @keatkeat87 abd @JusuVh. Maybe we should be able to decide when we want to get event emitted from In Angular v18 Case 1: Static Content <parent>
<child [value]="1" />
</parent> @Component(...)
export class ChildComponent {
value = model.required<any>();
}
@Component(...)
export class ParentComoponent {
private readonly children = contentChildren(ChildComponent);
constructor() {
toObservable(this.children).subscribe((children) => {
if (children.length > 0) {
console.log(children[0].value()); // Works! .required is accessable from here
}
});
}
} Case 2: Dynamic Content: <parent>
@defer {
<child [value]="1" />
} @loading (minimum 1000ms) {
Loading....
}
</parent> @Component(...)
export class ChildComponent {
value = model.required<any>();
}
@Component(...)
export class ParentComoponent {
private readonly children = contentChildren(ChildComponent);
constructor() {
toObservable(this.children).subscribe((children) => {
if (children.length > 0) {
console.log(children[0].value()); // Error! Input is required but no value is available
}
});
}
} As I said - this is unexpected and a little bit unlogical for our team. We need to convert back all contentChildren/viewChildren to @ViewChildren @ContentChildren and listen for .changes which is a big regression. export class ParentComponent{
@ContentChildren(ChildComponent)
private children!: QueryList<ChildComponent>;
private readonly destroyRef = inject(DestroyRef);
constructor() {
afterRenderEffect(() => {
this.children.changes
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
const children = this.children2.toArray();
if (children.length > 0) {
console.log(children[0].value()); // Works.
}
});
});
}
} EDIT: function toObservableAfterRender<T>(signal: Signal<T>) {
const destroyRef = inject(DestroyRef, {
optional: true,
});
const subject = new Subject<T>();
if (destroyRef != null) {
destroyRef.onDestroy(() => {
subject.complete();
});
}
afterRenderEffect(() => {
subject.next(signal());
});
return subject.asObservable();
} Use case: export class ParentComponent{
private readonly children = contentChildren(ChildComponent);
constructor() {
toObservableAfterRender(this.children).subscribe((children) => {
if (children.length > 0) {
console.log(children[0].value()); // Now works
}
});
}
} |
@alxhub |
What with this issue? This is a huge breaking-change with basic behaviors of angular which is forcing to remove I'm still confusing why no one from Angular Team didn't explain this behavior since Dec 2024. |
It's not really an issue with This is something we can look into fixing on the query side. A workaround is to filter out uninitialized results yourself, by e.g. adding an |
@alxhub, thank for reply. but I think you’re not very clear about the situation. I'm not sure whether you have time to discuss this issue in more depth. Since effects will be stabilized in v20, I just don’t want the issue to end up being solved in an incorrect way due to the restriction of no breaking changes in the future. I'm not sure if you’ve looked at all the examples I mentioned above? but if you’re willing to continue the discussion after JeanMeche last reply, I’d be happy to do so. |
Is it possible that this is happening synchronously (without micro/macro tasks):
If my guess is incorrect, please ignore my question. |
Which @angular/* package(s) are the source of the bug?
Don't known / other
Is this a regression?
Yes
Description
Just updated to Angular v19 and started to experience an error. Created an example with as minimal code as possible. Unfortunately it doesn't make too much sense without additional context, but it is what it is.
So the setup is the following:
ModelDirective
)ModelDirective
and uses its required input (FormFieldComponent
)The problem is observed within the
toObservable
but only if the queried directive is created dynamically within a dynamically created component. If you run the example, you can see that the input is actually set properly - if we directly use the computed signal, its value is successfully displayed. However, once we try to read the observable's value, an error is thrown.If, however, the directive or the owning component is available without any condition, everything works fine (there is a commented line in the example template).
This setup was working fine in Angular v18.
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/stackblitz-starters-vjskep?file=src%2Fmain.ts
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run
ng version
)Anything else?
No response
The text was updated successfully, but these errors were encountered: