question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

@ContentChild doesn't work in @angular/elements with slots

See original GitHub issue

Which @angular/* package(s) are the source of the bug?

core, elements

Is this a regression?

No

Description

I apologize in advance for the longer post. I’m trying to convert a reusable angular component into a web component using @angular/elements. Everything works fine up to the point where I try to expose slots through the parent component, which have to be accessed through it. Everything works great in the angular application context, but the elements build cannot recognize the child slots as @ContentChildren(). I have created a small example to explain the issue I’m facing.

I have a parent component looking like this:

wrapper.component.html:

<h1>Wrapper's own content</h1>

<button (click)="changeChildText()">Change child text</button>

<ng-content select="app-child-standalone"></ng-content>

wrapper.component.ts:

import { Component, ContentChild } from '@angular/core';
import { ChildStandaloneComponent } from './child-standalone/child-standalone.component';

@Component({
  selector: 'app-wrapper',
  templateUrl: './wrapper.component.html',
  styleUrls: ['./wrapper.component.scss']
})
export class WrapperComponent {
    // ContentChild evaluated properly in the angular application
    // but is undefined in plain html
    @ContentChild(ChildStandaloneComponent)
    public child: ChildStandaloneComponent;

    constructor() { }

    public changeChildText() {
      if (this.child) {
        this.child.text = "Parent modified text.";
      }
    }
}

The child component looks like this:

child-standalone.component.html:

<p>child-standalone's own static content</p>

<span>{{text}}</span>

<ng-content></ng-content>

child-standalone.component.ts:

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-child-standalone',
  templateUrl: './child-standalone.component.html',
  styleUrls: ['./child-standalone.component.scss']
})
export class ChildStandaloneComponent {
    @Input()
    public text = "Default input text.";

    constructor() { }
}

I build both components as elements and use them in a plain html:

  ngDoBootstrap() {
    const wrapper = createCustomElement(WrapperComponent, { injector: this.injector });
    customElements.define("app-wrapper", wrapper);

    const child = createCustomElement(ChildStandaloneComponent, { injector: this.injector });
    customElements.define("app-child-standalone", child);
  }
<body>
    <app-wrapper>
        <app-child-standalone id="childComp">
            <button onclick="changeText()">Change from child</button>
        </app-child-standalone>
    </app-wrapper>

    <script>
        let changeText = () => {
            childComp.text = 'Changed directly from child through ng-content.';
        }
    </script>
</body>

The line with the ContentChild is the one that isn’t working in the app-wrapper element and I cannot get the parent component to manipulate the child component because the ContentChild is undefined. How can I make my components evaluate user-provided child slot content in elements as well?

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 13.2.5
Node: 16.11.0
Package Manager: npm 8.1.3
OS: win32 x64

Angular: 13.2.5
... animations, cli, common, compiler, compiler-cli, core
... elements, forms, language-service, platform-browser
... platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1302.5
@angular-devkit/build-angular   13.2.5
@angular-devkit/core            13.2.5
@angular-devkit/schematics      13.2.5
@schematics/angular             13.2.5
ng-packagr                      13.2.1
rxjs                            6.6.7
typescript                      4.5.5

Anything else?

I was actually able to work this around and make it work in both angular and web component scenarios, but the workaround seems like a hack to me. Let me know if this is how I should be doing this, or whether this is simply a bug, or potentially a feature request.

import { Component, ContentChild } from '@angular/core';
import { ChildStandaloneComponent } from './child-standalone/child-standalone.component';

@Component({
  selector: 'app-wrapper',
  templateUrl: './wrapper.component.html',
  styleUrls: ['./wrapper.component.scss']
})
export class WrapperComponent {
    @ContentChild(ChildStandaloneComponent)
    public child: ChildStandaloneComponent | Element;

    // This works because once I see the child component is not evaluated correctly, I extract it from the DOM
    public get childComponent(): ChildStandaloneComponent | Element {
        if (!this.child) {
            const collection = document.getElementsByTagName('app-child-standalone');
            this.child = collection?.length ? collection.item(0) : null;
        }
        return this.child
    }

    constructor() { }

    public changeChildText() {
      if (this.childComponent) {
        (this.childComponent as ChildStandaloneComponent).text = "Parent modified text.";
      }
    }
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:6
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
JoostKcommented, Mar 16, 2022

The selector of a ContentQuery is currently used for specyfing either a directive or reference name, it’s not a CSS-like selector like @Component.selector is. I don’t know if this could be supported in the future.

0reactions
angular-automatic-lock-bot[bot]commented, Apr 16, 2022

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

Read more comments on GitHub >

github_iconTop Results From Across the Web

ContentChildren is not being populated when parent render ...
I have three components: Panel, PanelGroup ( ...
Read more >
Angular ng-content and Content Projection: A Complete Guide ...
We are going to see how content projection works, when to use it and why, and how it can improve a lot the...
Read more >
Angular Components In Depth 22 - Multi slot content projection
Access the full course here: https://javabrains.io/courses/angular_componentsindepth Learn how you can do content projection for multiple ...
Read more >
angular/angular - Gitter
Dynamically rendered content isn't being transcluded to the correct slot ... ContentChildren doesn't seem to work at all for dynamically rendered content.
Read more >
ngx-dynamic-hooks - npm Package Health Analysis - Snyk
Automatically insert live Angular components into a dynamic string of content (based on their selector or any pattern of your choice) and render...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found