angular: It seems that ng-content replacement does not work for elements selected by element name, after RC6

[x ] bug report

Current behavior It seems that ng-content replacement behaviour is broken on RC-6 for elements selected by element name, for example:

<ng-content select="element-name"></ng-content>

But works for

<ng-content select=".class-name"></ng-content>

Sample error message:

'modal-content' is not a known element:
1. If 'modal-content' is an Angular component, then verify that it is part of this module.
2. If 'modal-content' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schema' of this component to suppress this message. ("-modal title="Incluir grupo econômico" #addCustGroupDialog [approve]="approveNewCustomerGroup">
    [ERROR ->]<modal-content>
      <div class="ui fluid input">
        <input class="ui input" type="text" [(ngMo"): CustomerGroups@6:4

Expected/desired behavior Normal behaviour verified before RC6. As described in this article: https://toddmotto.com/transclusion-in-angular-2-with-ng-content

Reproduction of the problem https://plnkr.co/edit/K1UYuVEMyxmMIhmWz4nl?p=preview

What is the expected behavior? Normal behaviour verified before RC6.

What is the motivation / use case for changing the behavior?

Please tell us about your environment:

  • Angular version: 2.0.0-rc.6
  • Browser: [Chrome 52, Firefox 48.0.2]
  • Language: [TypeScript]

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 5
  • Comments: 26 (10 by maintainers)

Most upvoted comments

I suppose the compiler could see the <ng-content select="X"...> and let the 'X` tags through the element check when used in that context (and that context only).

That seems plausible. Maybe we should consider that idea. I’ll run it by some people.

Seems like your errors is:

'modal-content' is not a known element:
1. If 'modal-content' is an Angular component, then verify that it is part of this module.
2. If 'modal-content' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schema' of this component to suppress this message. ("-modal title="Incluir grupo econômico" #addCustGroupDialog [approve]="approveNewCustomerGroup">
    [ERROR ->]<modal-content>
      <div class="ui fluid input">
        <input class="ui input" type="text" [(ngMo"): CustomerGroups@6:4

if <modal-content> is an angular cmp, is should be in your test modules includes

Wow strong opinions @wardbell … To assume everyone else would agree with you is dangerous, in any context.

Personally, I don’t like the use of CSS classes for transcluding content because simply put… CSS classes are CSS classes, they are for applying styles. Using them for anything else hurts the readability of the code, and as developers we have a duty to speak to other developers through our code and not just to the Angular compiler.

From this point of view, custom tag elements is a better solution but not optimal. In my opinion, adding an directive for the parent component that would pair with the existing directive for the child component would offer the best readability.

@Component({
  selector: 'my-app',
  template: `
    <h2>Enabling Arbitrary Element Tags</h2>
    <my-component2>
      <ng-content select="component-title">
        This is the Component2 title!
      </ng-content>
      <ng-content select="component-content">
        And here's some awesome Component2 content.
      </ng-content>
    </my-component2>
  `
})
export class AppComponent2 { }

@Component({
  selector: 'my-component2',
  template: `
    <div class="my-component">
      <div>
        Title:
        <ng-content select="component-title"></ng-content>
      </div>
      <hr>
      <div>
        Content:
        <ng-content select="component-content"></ng-content>
      </div>
    </div>
  `
})
export class MyComponent2 { }

Now when someone else sees this code, they will know straight away that this is content that will be transcluded into the child component. Whereas if they are CSS selectors then their first instinct will probably be to look for matching CSS classes, and if they are custom element tags then the first instinct will probably to look for matching components.

I’d also like to see this fixed. I have two reasons for using element selectors for my transclusion locations, both of which I believe are valid:

  • The semantic nature of the markup makes it very easy to understand (or “reason about” as all the cool kids say these days). The child elements match the parent elements and it’s obvious they go together and that they are hierarchical. For example, this:
<my-tile>
  <my-tile-header>Header</my-tile-header>
  <my-tile-content>Content</my-tile-content>
</my-tile>

is much more intuitive and easier to document than this:

<my-tile>
  <div class="my-tile-header">Header</div>
  <div class="my-tile-content">Content</div>
</my-tile>
  • While these elements aren’t components today, they may very well be components tomorrow. If they’re already custom elements then I can do that without the consumers of my open source component library even caring about it. If they use CSS classes then I either have to get everybody using the library to change their code or end up with a component library with half of its components using element selectors and the other half using CSS selectors, making the API very confusing.

Thanks @vicb!

Actually, the whole problem can be easily avoided if you use a class selector instead of element tags to denote the projected content. This is my favorite solution and requires NO changes and NO configuration.

You simply have to be willing to change your technique very slightly. I cannot imagine why this would be objectionable.

I updated the plunker to demonstrate

For convenience, the key class changes are here:

// USE A CLASS SELECTOR INSTEAD OF ELEMENT SELECTOR
@Component({
  selector: 'my-app',
  template: `
    <h2>Enabling Arbitrary Element Tags</h2>
    <my-component2>
      <div class="component-title">
        This is the Component2 title!
      </div>
      <div class="component-content">
        And here's some awesome Component2 content.
      </div>
    </my-component2>
  `
})
export class AppComponent2 { }

// Use class selector instead of element tag.
// Projects content in divs with special class names
// into two matching named <ng-content> slots
// The class names are part of the selectors.
@Component({
  selector: 'my-component2',
  template: `
    <div class="my-component">
      <div>
        Title:
        <ng-content select=".component-title"></ng-content>
      </div>
      <hr>
      <div>
        Content:
        <ng-content select=".component-content"></ng-content>
      </div>
    </div>
  `
})
export class MyComponent2 { }

I’m not worried about classes impacting styles outside the component. I’m concerned about external CSS rules set on tag names and impacting styles inside the component. It’s quite common to have rules such as parent-component div { ... }. This is not something that NgModule can prevent as far as I know.

And no, with all due respect, “just switch to classes” is not an acceptable option for everyone. Almost all the articles about ng-content uses custom elements in their examples, so the users do. Some popular open source components rely on it too. You can’t tell all these people to change their API because RC6 does not cover this (basic) case.

The other workarounds you proposed are much more helpful, yet not totally satisfying. There is still an issue to be solved here, so we’ll just have to wait for the improvements on NgModule schemas.

@wardbell : using custom elements is a way to ensure CSS isolation on the host element, and emphasises transcluded content in the templates.