When a view has more than one effect scheduled to run, and one of them
destroys the view (e.g. by calling `componentRef.destroy()`), the
remaining effects in that same flush could still run afterward,
against a view that no longer exists. In some cases this crashed
outright with `TypeError: view[EFFECTS] is not iterable`.
Here's why: `runEffectsInView` walks a view's effects in a `for...of`
loop, wrapped in an outer `while` loop that re-checks for any effects
that became dirty as a side effect of ones that already ran. When an
effect destroys its view, `view[EFFECTS]` gets set to `null` as part
of tearing the view down.
First attempt checked for that inside the `for...of` loop, before each
effect runs. That covers a sibling effect later in the *same* pass,
but misses a second case: if the effect that destroys the view *also*
dirties another effect on that same view in the process (e.g. by
writing a signal the sibling depends on), the outer `while` loop sees
`HasChildViewsToRefresh` set and tries to restart — and immediately
crashes re-entering `for (const effect of view[EFFECTS])` on a
now-null value, before the in-loop check ever gets a chance to run.
Reproduced that exact crash with a test first: two effects on one
view, the second one writes a signal the first depends on and then
destroys the view in the same call — confirmed it throws before
touching the fix.
Fixed by checking right after `effect.run()` instead of before it,
covering both cases in one place: the remaining effects in the current
pass, and the loop trying to restart afterward. As soon as one effect
destroys the view, nothing else runs against it again.
This is intentionally narrow in scope. An earlier version of this fix
also tried to guarantee that `onCleanup()` callbacks still ran even
when registered after an effect destroyed its own view. That's been
dropped — destroying your own view and then continuing to register
more work for it isn't something the framework should have to paper
over. If you need to do both, register `onCleanup` first, then
destroy.
(cherry picked from commit c658d73210)
Angular - The modern web developer's platform
Angular is a development platform for building mobile and desktop web applications
using TypeScript/JavaScript and other languages.
Contributing Guidelines
·
Submit an Issue
·
Blog
Documentation
Get started with Angular, learn the fundamentals and explore advanced topics on our documentation website.
Advanced
Local Development
To contribute to the Angular Docs, check out the Angular.dev README
Development Setup
Prerequisites
- Install Node.js which includes Node Package Manager
Setting Up a Project
Install the Angular CLI globally:
npm install -g @angular/cli
Create a workspace:
ng new [PROJECT NAME]
Run the application:
cd [PROJECT NAME]
ng serve
Angular is cross-platform, fast, scalable, has incredible tooling, and is loved by millions.
Quickstart
Ecosystem
Changelog
Learn about the latest improvements.
Upgrading
Check out our upgrade guide to find out the best way to upgrade your project.
Contributing
Contributing Guidelines
Read through our contributing guidelines to learn about our submission process, coding rules, and more.
Want to Help?
Want to report a bug, contribute some code, or improve the documentation? Excellent! Read up on our guidelines for contributing and then check out one of our issues labeled as help wanted or good first issue.
Code of Conduct
Help us keep Angular open and inclusive. Please read and follow our Code of Conduct.
Community
Join the conversation and help the community.
- X (formerly Twitter)
- Bluesky
- Discord
- YouTube
- StackOverflow
- Find a Local Meetup
Love Angular? Give our repo a star ⭐ ⬆️.
