Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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#69104
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

@leonsenft

Copy link
Copy Markdown
Contributor

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.

leonsenft added 8 commits June 1, 2026 08:49
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.
@leonsenft leonsenft added the area: compiler Issues related to `ngc`, Angular's template compiler label Jun 3, 2026
@ngbot ngbot Bot added this to the Backlog milestone Jun 3, 2026
@angular-robot angular-robot Bot added the area: core Issues related to the framework runtime label Jun 3, 2026
…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.
@leonsenft leonsenft force-pushed the fix-out-of-scope-foreign-components branch from b170883 to 3b1e4d4 Compare June 3, 2026 00:20
Comment thread packages/compiler/src/template/pipeline/src/ingest.ts
@leonsenft

Copy link
Copy Markdown
Contributor Author

Folded into #69087

@leonsenft leonsenft closed this Jun 5, 2026
@leonsenft leonsenft deleted the fix-out-of-scope-foreign-components branch June 5, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.