Skip to content

ng-content default content #12530

Closed
Closed

Description

@mbeckenbach

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
When you add something inside a ng-content tag you get an error message: element cannot have content.

Expected behavior
Should render the html inside the ng-content tag as a default if no content was given.

Minimal reproduction of the problem with instructions
Create a component that uses ng-content somewhere. Put some html inside, that should be rendered when nothing else is 'injected' there from outside. Then use the component somewhere else.

What is the motivation / use case for changing the behavior?
In some cases you need a default behavior that can be replaced. In my case its an app header that shows the logo. When you navigate to a sub-page the logo will be replaced by a back button. It feels strange to add something like an input property to hide or show the logo. Some default content for ng content placeholders would feel much more intuitive.

Please tell us about your environment:
Win10, Angular CLI beta 18, VS Code

  • Angular version: 2.1.0
    2.1.0
  • Browser:
    All
  • Language:
    TS
  • Node (for AoT issues): node --version =

Activity

  1. mbeckenbach commented on Oct 25, 2016

    @mbeckenbach
    Author

    If i remember correctly thats the way multi slot transclusion works in ng 1

  2. added
    featureLabel used to distinguish feature request from other issues
    area: coreIssues related to the framework runtime
    on Oct 26, 2016
  3. webmutation commented on Jan 12, 2017

    @webmutation

    Is there any other way to have a fallback-content for the slot? I found a workaround but its quite ugly

    <div #ref><ng-content></ng-content></div> 
               <span *ngIf="ref.childNodes.length == 0">
                  Display this if ng-content is empty!
               </span>
    
  4. DzmitryShylovich commented on Jan 12, 2017

    @DzmitryShylovich
    Contributor

    not yet

  5. emilio-martinez commented on Apr 25, 2017

    @emilio-martinez
    Contributor

    I don't know if adding something inside ng-content is really the best approach for this, but I agree that a fallback value would be desirable. Perhaps it could interop with ngIf Else Then?

  6. DrewLandgrave commented on May 24, 2017

    @DrewLandgrave
  7. AndrinGautschi commented on Aug 5, 2017

    @AndrinGautschi

    Anything new on this feature request?

  8. acaua commented on Sep 15, 2017

    @acaua

    @webmutation , theres a slight mistake in your solution: you should test if "ref.children.length == 0", not if "ref.childNodes.length == 0"

    It should be like this:

    <div #ref><ng-content></ng-content></div> 
    <span *ngIf="ref.children.length == 0">
      Display this if ng-content is empty!
    </span>
  9. webmutation commented on Sep 15, 2017

    @webmutation

    @acaua Actually that is not my code, but I agree node.children should be used and not node.childNodes, not that it is a mistake but falsely assumes that child nodes are of type Element. Even though it would be reasonable to assume that whatever is projected in ng-content would be of type Element, it can create issues, therefore the proper API should be used, and since node.children is supported for IE9+ cross browser support also is not an issue.

    Node.children is a read-only property that returns a live HTMLCollection of the child elements of Node.

    The Node.childNodes read-only property returns a live collection of child nodes of the given element where the first child node is assigned index 0.
    To get a collection of only elements, use ParentNode.children instead.

    If we look at the polyfill for node.children we see the use of childNodes but it is filtered by node.nodeType === 1 (Where nodeType ===1 represents An Element node such as < p >or < div > ), so node.children should always work even when it needs to be polyfilled.

    Object.defineProperty(constructor.prototype, 'children', {
                get: function() {
                    var i = 0, node, nodes = this.childNodes, children = [];
                    while (node = nodes[i++]) {
                        if (node.nodeType === 1) {
                            children.push(node);
                        }
                    }
                    return children;
                }
            });
    

    @mbeckenbach You are correct that is the way it works in ng1, therefore it would be nice to keep it consistent, unless there is a good reason to make it different.

    Still hopping the ng1 behavior as suggested by OP gets added, since it was straightforward to use.

  10. didii commented on Oct 6, 2017

    @didii

    Keep in mind that both children and childNodes will have different behavior depending on how you use them. See the examples below to decide which workaround you want to currently use.


    node.children only works when another element is added. Not if only text is present.
    Example:

    <default-component>Foo</default-component>
    

    where default-component has the template as @acaua mentions:

    <div #ref><ng-content></ng-content></div> 
    <span *ngIf="ref.children.length == 0">
      Display this if ng-content is empty!
    </span>
    

    This will result into

    Foo
    Display this if ng-content is empty!


    This is not the case when you use childNodes. In that case, the opposite happens if the tag has any whitespace or comment.
    Example:

    <default-component><!--hi--></default-component>
    

    or

    <default-component>
    </default-component>
    

    or

    <default-component> </default-component>
    

    with as template

    <div #ref><ng-content></ng-content></div> 
    <span *ngIf="ref.childNodes.length == 0">
      Display this if ng-content is empty!
    </span>
    

    will all result into nothing that is displayed.


    EDIT

    Using childNodes we can however check it with the following function:

    isEmpty(element: HTMLElement): boolean {
        const nodes = element.childNodes;
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes.item(i);
            if (node.nodeType !== 8 && nodes.item(i).textContent.trim().length !== 0) {
                return false;
            }
        }
        return true;
    }
    

    This will ignore nodes that are only whitespace and are comments (nodeType 8).

  11. jack-bliss commented on Jan 18, 2018

    @jack-bliss

    being able to determine wether or not ng-content exists seems very important, and having an option that works with unviersal would be very nice.

  12. added this to the Backlog milestone on Jan 23, 2018
  13. benneq commented on Mar 17, 2018

    @benneq

    I have a similar requirement:
    <h1><ng-content select="[title]"></ng-content></h1>
    I want to completely remove the <h1> if there's no content set.

    Though I need something like this:
    <h1 *ngIf="titleRef"><ng-content select="[title]" #titleRef></ng-content></h1>

  14. 46 remaining items

  15. Den-dp commented on Sep 14, 2023

    @Den-dp
    Contributor

    I was surprised that this doesn't work the same way as NgTransclude with a fallback content (angular.js)

  16. vneogi199 commented on Nov 27, 2023

    @vneogi199
    Contributor

    Can we have something like the default placeholder for ng-content, similar to defer placeholder

  17. LPCmedia commented on Mar 9, 2024

    @LPCmedia

    The design choice of having a "default behavior" that needs to be overridden in some cases seems like misguided inheritance. When a component has an input that basically is an if/else its likely 2 components or if absolutely needed conditional content projection. This means you have to bind this input for all sub-routes as well...

    On the other hand: its unclear why ng-content wouldn't have the same behavior as a <slot> in a standard web-component.

  18. added a commit that references this issue on Mar 25, 2024
    0d7e5de
  19. added a commit that references this issue on Mar 26, 2024
    a600a39
  20. nelson6e65 commented on Apr 19, 2024

    @nelson6e65

    Only for Angular 18? 👀

  21. angular-automatic-lock-bot commented on May 20, 2024

    @angular-automatic-lock-bot

    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.

  22. locked and limited conversation to collaborators on May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: coreIssues related to the framework runtimecore: content projectionfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under considerationfreq3: highstate: has PR

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions