fix(compiler): support foreign components defined outside top-level scope#69104
Closed
leonsenft wants to merge 9 commits into
angular:mainangular/angular:mainfrom
leonsenft:fix-out-of-scope-foreign-componentsleonsenft/angular:fix-out-of-scope-foreign-componentsCopy head branch name to clipboard
Closed
fix(compiler): support foreign components defined outside top-level scope#69104leonsenft wants to merge 9 commits intoangular:mainangular/angular:mainfrom leonsenft:fix-out-of-scope-foreign-componentsleonsenft/angular:fix-out-of-scope-foreign-componentsCopy head branch name to clipboard
leonsenft wants to merge 9 commits into
angular:mainangular/angular:mainfrom
leonsenft:fix-out-of-scope-foreign-componentsleonsenft/angular:fix-out-of-scope-foreign-componentsCopy head branch name to clipboard
Conversation
Add descriptive text to foreign view head and tail comments in dev mode to assist in debugging.
Implement the `ɵɵforeignComponent` instruction to render foreign components (components from other frameworks) inside Angular templates. The instruction creates a host LContainer, instantiates a foreign view, executes the foreign component's RENDER function, inserts the returned native DOM nodes, and registers the disposal hook. Add unit tests to verify element rendering, property passing, dependency injection, and disposal on destruction.
…nt props Avoid triggering the `interpolated_signal_not_invoked` diagnostic when a signal is passed directly as a property binding to a foreign component. Foreign components may accept signals directly, so they should not be flagged as uninvoked in this context. To support testing this, the typecheck testing infrastructure was updated to allow defining mock foreign components in the test setup.
Previously, the `ɵɵforeignComponent` instruction set the `currentTNode` state during the first template creation pass (via `getOrCreateTNode`), but failed to do so on subsequent instantiations when the `TNode` was accessed from cache. This resulted in the global `currentTNode` state remaining unchanged from the previous instruction. When closing a parent element (e.g., via `ɵɵelementEnd`), this mismatched state caused assertion failures because the framework attempted to close the wrong parent node. This change fixes the issue by calling `setCurrentTNode(tNode, false)` when the foreign component's `TNode` is retrieved from the cache.
Previously, any children nested inside a foreign component were ignored during template ingestion. With this change, the compiler now: 1. Identifies when a foreign component has children in the template AST. 2. Compiles these children into a separate template view (using the standard TemplateOp). 3. Passes a `ɵɵforeignContent` expression under the `children` prop inside the foreign component's `props` object. At runtime, the new `ɵɵforeignContent(index)` instruction instantiates the template at the specified slot index in memory (detached from the DOM), extracts its root DOM nodes, and returns them. These root nodes are then passed directly to the foreign component's `props.children` so they can be rendered by the foreign framework. The instantiated children view is registered in the parent LView's child tree, ensuring its change detection and destruction are managed automatically as part of the standard Angular view tree lifecycle.
…nent props Add `@content(propName)` blocks for passing template content to foreign component properties by name. Previously, only a single set of direct children could be passed to a foreign component via the default `children` property. With this change, developers can project distinct template content to multiple specific properties on the foreign component: ```html <FancyButton [label]="title"> @content(icon) { <span>Icon</span> } @content(description) { <span>Description text</span> } <span>Other children</span> </FancyButton> ``` Specifically: - Add support to the HTML lexer for `@content` blocks. - Introduce `ContentBlock` AST node to represent `@content` blocks. - Implement validation ensuring `@content` blocks have exactly one parameter representing a valid JS identifier. - Throw an error during ingestion if a `@content` block is placed anywhere other than as a direct child of a foreign component. - Map `@content` blocks to properties of the props object passed to `ɵɵforeignComponent`. - Update compliance and unit tests to cover these changes. ```
This commit introduces a logical-only container flag (`LContainerFlags.LogicalOnly`)
to support Angular features (like change detection and queries) on projected content
within foreign components, while relinquishing control over their placement in the DOM.
When content is projected into a foreign component via `ɵɵforeignContent`, the foreign
component receives the native DOM nodes directly and assumes control over their DOM
placement. Therefore, Angular must skip all platform-level view operations (insert,
move, delete) on these projected views.
To achieve this:
1. Introduce Logical-Only Containers:
- Added `LContainerFlags.LogicalOnly` to represent view containers whose nodes are
managed logically (by the consuming foreign component) rather than by the renderer.
- Flagged `ɵɵforeignContent` containers with the `LogicalOnly` annotation.
- Updated `applyContainer` in `node_manipulation.ts` to return early and skip platform
DOM manipulations (insert, detach, destroy) on containers marked as logical-only.
2. Guard `collectNativeNodes`:
- Updated `collectNativeNodes` in `collect_native_nodes.ts` to skip descending into
logical-only containers. This prevents nested projected child elements (which are
already claimed and placed inside nested foreign components) from being re-collected
at the parent component's projection root level.
3. Unit and Acceptance Tests:
- Added a comprehensive set of categorized acceptance tests in `foreign_component_spec.ts`
covering nested foreign projections, projecting foreign components into Angular components,
Signal-based view queries (`viewChildren`), event handlers, and change detection.
Previously, foreign component `@content` blocks were rendered eagerly by Angular and could only project a list of nodes. With this change, `@content` can be used to declare a function (e.g. `@content(renderItem; let item)`) that is passed as a callback prop to the foreign component, allowing the foreign component to invoke it with context arguments at its leisure. Implementation details: - Introduces a new runtime instruction `ɵɵforeignContentFn` which wraps the template function so it can be called on demand with arguments by the foreign component. - Extends the compiler AST to parse and validate `@content` parameters. - Maps `@content` parameters to the corresponding positional arguments of the calling foreign component function property.
…cope Currently, the template pipeline directly emits the raw expression for foreign component definitions (such as `frameworkImport(MyComponent)`) directly into the body of the generated template function. If a foreign component is defined inside a local scope or is non-exported (e.g. nested inside a test block), the emitted template function may not have access to that variable because `ɵɵdefineComponent` and its template functions are emitted at the top-level module scope. This previously caused reference errors during template compilation. This commit updates the compilation pipeline to instead ingest foreign component references into the component's `consts` pool. The `ɵɵforeignComponent` runtime instruction is updated to accept an index into the constant pool rather than a raw expression. By routing the references through the `consts` pool, block-scoped classes and variables are appropriately captured by `ngtsc` without scoping errors, properly supporting nested/local foreign component usage.
b170883 to
3b1e4d4
Compare
alxhub
reviewed
Jun 3, 2026
Contributor
Author
|
Folded into #69087 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Currently, the template pipeline directly emits the raw expression for foreign component definitions (such as
frameworkImport(MyComponent)) directly into the body of the generated template function. If a foreign component is defined inside a local scope or is non-exported (e.g. nested inside a test block), the emitted template function may not have access to that variable becauseɵɵdefineComponentand its template functions are emitted at the top-level module scope. This previously caused reference errors during template compilation.This commit updates the compilation pipeline to instead ingest foreign component references into the component's
constspool. TheɵɵforeignComponentruntime instruction is updated to accept an index into the constant pool rather than a raw expression. By routing the references through theconstspool, block-scoped classes and variables are appropriately captured byngtscwithout scoping errors, properly supporting nested/local foreign component usage.