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

Add support for projecting content into foreign component properties#68996

Merged
atscott merged 7 commits into
angular:mainangular/angular:mainfrom
leonsenft:foreign-component-childrenleonsenft/angular:foreign-component-childrenCopy head branch name to clipboard
Jun 5, 2026
Merged

Add support for projecting content into foreign component properties#68996
atscott merged 7 commits into
angular:mainangular/angular:mainfrom
leonsenft:foreign-component-childrenleonsenft/angular:foreign-component-childrenCopy head branch name to clipboard

Conversation

@leonsenft

@leonsenft leonsenft commented May 28, 2026

Copy link
Copy Markdown
Contributor

Previously, any children nested inside a foreign component were ignored during template ingestion. With this change, the compiler now:

  1. Parses any @content blocks inside the foreign component body. All other nodes are implicitly assigned to the children content block.
  2. Compiles each block into a separate template view (using the standard TemplateOp).
  3. Passes a ɵɵforeignContent expression for each content block to its corresponding property in the foreign component's props argument.

See individual commit messages for more detailed descriptions.

@leonsenft leonsenft added area: core Issues related to the framework runtime area: compiler Issues related to `ngc`, Angular's template compiler target: minor This PR is targeted for the next minor release labels May 28, 2026
@ngbot ngbot Bot added this to the Backlog milestone May 28, 2026
@leonsenft leonsenft force-pushed the foreign-component-children branch 3 times, most recently from e084e9b to 6955467 Compare June 2, 2026 05:45
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.
@leonsenft leonsenft force-pushed the foreign-component-children branch from 6955467 to 146e109 Compare June 3, 2026 20:07
@leonsenft leonsenft changed the title refactor(compiler): support passing children to foreign components Add support for projecting content into foreign component properties Jun 3, 2026
@leonsenft leonsenft requested review from atscott and mattrbeck June 3, 2026 20:14
@leonsenft leonsenft marked this pull request as ready for review June 3, 2026 20:14
@leonsenft

Copy link
Copy Markdown
Contributor Author

@alxhub I addressed your comment from #69104 (comment) in this PR.

@leonsenft leonsenft force-pushed the foreign-component-children branch from f634490 to ef8a9cd Compare June 3, 2026 20:28
@leonsenft leonsenft marked this pull request as draft June 3, 2026 20:40
…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.
```
@leonsenft leonsenft force-pushed the foreign-component-children branch from ef8a9cd to 2f221e3 Compare June 3, 2026 21:23
@leonsenft leonsenft marked this pull request as ready for review June 3, 2026 21:48

@atscott atscott left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENT: I have reviewed the rebased code and verified the line numbers. Here are my inline comments and suggestions.

edit: Thanks, agent for verifying the line numbers as I requested 😆

Comment thread packages/compiler/src/template/pipeline/src/ingest.ts Outdated
Comment thread packages/compiler/src/template/pipeline/src/ingest.ts

// 6. Insert the returned nodes into the foreign view, between its head and tail comment anchors.
const tail = viewRef.tail as RNode;
const parent = tail.parentNode;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the previous change, but Gemini noticed that this should be renderer.parentNode(tail)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine, since it's directly defined by RNode and documented that it can be used at runtime:

* At runtime we can then use the DOM api directly, in server or web-worker
* it will be easy to implement such API.
*/
/** Subset of API needed for appending elements and text nodes. */
export interface RNode {
/**
* Returns the parent Element, Document, or DocumentFragment
*/
parentNode: RNode | null;

@pullapprove pullapprove Bot requested review from JeanMeche and kirjs June 5, 2026 00:25

@JeanMeche JeanMeche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed-for: public-api

@leonsenft leonsenft force-pushed the foreign-component-children branch from 0a08871 to 3e99940 Compare June 5, 2026 00:59
leonsenft added 2 commits June 4, 2026 18:07
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.
…alysis

Refactors the unsupported bindings validation for foreign components from the
template semantics checker phase (during type-checking) to the component
analysis phase. Surfacing this check during component analysis means it will be
correctly reported during local compilation (which skips full template
type-checking).

Specifically:
- Creates a new helper `analyzeForeignComponentFeatures` in
  `foreign_component.ts` that traverses template elements and checks for
  unsupported outputs, references, and non-property inputs on foreign components.
- Removes the legacy validation from `template_semantics_checker.ts`.
- Invokes the validation during component analysis in
  `ComponentDecoratorHandler.analyze()`.
leonsenft added 2 commits June 4, 2026 18:07
Adds validation to verify that `@content` blocks are only used as direct
children of foreign components.

Specifically:
- Defines a new compile diagnostic code `INVALID_CONTENT_PLACEMENT = 8026`.
- Updates `ForeignComponentFeatureAnalyzer` to traverse content blocks and
  report `INVALID_CONTENT_PLACEMENT` diagnostics if they are placed
  incorrectly.
- Removes the raw error thrown during ingestion in
  `packages/compiler/src/template/pipeline/src/ingest.ts`.
- Adds integration tests in `template_typecheck_spec.ts`.
…plicit children

Defining a `@content (children)` block explicitly is unnecessary because
children should always be passed implicitly as direct nested content of the
foreign component. Using an explicit block could also lead to conflicts and
silent template rendering issues where implicit content (like whitespace)
accidentally overwrote the explicit block in the compiler's template
representation.

This change introduces a compilation error
(`FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN`) when an explicit
`@content (children)` block is detected, guiding developers to pass children
implicitly instead.
@leonsenft leonsenft requested review from atscott and mattrbeck and removed request for kirjs June 5, 2026 01:14
@leonsenft

Copy link
Copy Markdown
Contributor Author

@mattrb @atscott Thanks for the great suggestions. I implemented all of the suggested checks, and moved the existing ones into the analyze phase, so these will be raised eagerly in local compilation.

@leonsenft leonsenft added target: patch This PR is targeted for the next patch release target: minor This PR is targeted for the next minor release and removed target: minor This PR is targeted for the next minor release target: patch This PR is targeted for the next patch release labels Jun 5, 2026
Ensures `@content` blocks on foreign components have unique names and do not
conflict with static attributes or input property bindings.

Specifically, this commit introduces two new template diagnostics:
1. `CONFLICTING_CONTENT_DECLARATION` (8028): Raised when multiple `@content`
   blocks with the same name are defined under the same foreign component.
2. `CONFLICTING_CONTENT_AND_PROPERTY` (8029): Raised when a `@content` block's
   name matches an attribute or input property binding on the parent foreign
   component.

Both diagnostics include related information pointing to the location of the
conflicting declaration or property.
@leonsenft leonsenft force-pushed the foreign-component-children branch from 3e99940 to 8821d3b Compare June 5, 2026 01:50
this.currentParent = prevParent;
}

private validateForeignComponent(element: TmplAstElement): void {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AOT-only Validation Discrepancy

Note: This is not a blocker for this PR and can be addressed in a follow-up.

Currently, the validation logic (duplicate content blocks, conflicts with property bindings) is implemented inside the compiler-cli AOT analysis step. In JIT compilation mode, these checks will not be executed. This means JIT users will experience silent runtime misbehavior (e.g. duplicate content blocks overwriting each other in the properties map) instead of clear compilation errors.

In other parts of Angular, JIT compilation performs parallel checks to approximate AOT validation errors. For example:

Consider adding a lightweight validation or JIT assert check (e.g. during runtime properties mapping or block instantiation) in a future PR to align JIT and AOT behaviors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fortunately this feature doesn't support JIT 😄

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sweet, I incorrectly assumed that the instruction presence in

'ɵɵforeignComponent': r3.ɵɵforeignComponent,
'ɵɵforeignContent': r3.ɵɵforeignContent,
meant we intended to support JIT 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point. Earlier during the implementation of this feature there was an explicit error throw if you tried importing foreign components in JIT. Due to refactor how we collected those, we ended up removing that error case... I actually realize now I have no idea what would happen if you try to use it in JIT mode. I'll investigate later.

@atscott atscott added the action: merge The PR is ready for merge by the caretaker label Jun 5, 2026
@ngbot

ngbot Bot commented Jun 5, 2026

Copy link
Copy Markdown

I see that you just added the action: merge label, but the following checks are still failing:
    failure status "google-internal-tests" is failing

If you want your PR to be merged, it has to pass all the CI checks.

If you can't get the PR to a green state due to flakes or broken main, please try rebasing to main and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help.

@atscott atscott merged commit 79e5d5d into angular:main Jun 5, 2026
21 of 24 checks passed
@atscott

atscott commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This PR was merged into the repository. The changes were merged into the following branches:

text: 'Child nodes are defined here.',
start: firstChild.sourceSpan.start.offset,
end: firstChild.sourceSpan.end.offset,
sourceFile: this.sourceMapping.node.getSourceFile(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENT: For external templates (using templateUrl), the main diagnostic is reported on the HTML template file. However, secondary "related messages" in foreign_component.ts are constructed passing the component's TypeScript file:

sourceFile: this.sourceMapping.node.getSourceFile()

But they use character offsets from the HTML template file (e.g., firstChild.sourceSpan.start.offset). This mismatch causes IDEs and CLI outputs to map HTML template offsets onto the TypeScript file, resulting in corrupted warning/error locations (pointing to incorrect or out-of-bound characters in the .ts file).

  1. Modify makeTemplateDiagnostic's signature in diagnostic.ts to make the sourceFile property in relatedMessages optional.
  2. In makeTemplateDiagnostic's implementation, default relatedMessage.sourceFile to the template's source file (sf ?? mapping.node.getSourceFile()) when it is not provided.
  3. Remove the explicit sourceFile: this.sourceMapping.node.getSourceFile() mapping from all related messages in foreign_component.ts to allow them to fall back to the template file.

@leonsenft leonsenft deleted the foreign-component-children branch June 5, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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