Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

ngRepeat should automatically or optionally render a fallback element when the expression yields no results #5919

Closed

Description

@matsko

Say you have a complex expression on ngRepeat like:

<div ng-repeat="item in getItems() | filter:values">...</div>

So how can you detect if there are no items returned after the filter or from the getItems() call without using a scope variable/method? You would have to move everything into a controller.

Despite the repeat expression being complex and a potential bottleneck for an application, extracting the calculation out into a controller and manually watching the data set, filtering it and aggregating it is a lot of extra code after having just created a nice filtering system for your ngRepeat code.

What would be nice is if a fallback DIV was created only when the total (final) results extracted from the ngRepeat expression equal 0. This way you can do something like.

<div ng-repeat="item in getItems() | filter:values">
  <div ng-repeat-empty>
    There are no results found
  </div>
</div>

Activity

  1. IgorMinar commented on Jan 21, 2014

    @IgorMinar
    Contributor

    Can anyone think of a different syntax that we should consider?

    I'd like to compare several syntaxes before we settle on the final api.

  2. scottopherson commented on Jan 21, 2014

    @scottopherson

    Nice, I really like this idea and ng-repeat-empty seems pretty elegant. I can't come up with any other syntaxes but I'd also like to see some alternatives. I assume it would work with .ng-enter/.ng-leave as well?

  3. matsko commented on Jan 22, 2014

    @matsko
    ContributorAuthor
  4. Micka33 commented on Jan 22, 2014

    @Micka33

    Like it. That would be nice to be able to set the ng-repeat-empty template using a URL as well.

  5. scottopherson commented on Jan 22, 2014

    @scottopherson

    @Micka33 I suppose <div ng-repeat-empty ng-include="'some-template.html'"></div> would work.

  6. benjamingr commented on Jan 31, 2014

    @benjamingr
    Contributor

    👍 this is something I constantly have to deal with with the current workaround and having it baked into Angular seems appropriate given how common the use case is.

  7. eternalmatt commented on Feb 3, 2014

    @eternalmatt

    Great idea. Proposing two other syntaxes for perspective

    1. As a sibling element
    <div ng-repeat="item in getItems() | filter:values"></div>
    <div ng-repeat-empty>There are no results found</div>
    
    1. @scottopherson's syntax; optionally assigning ng-repeat-empty a value is equivalent to the ng-include foo
    <div ng-repeat="item in getItems() | filter:values">
        <div ng-repeat-empty="'some-template.html'"></div>
    </div>
    

    Inlining above syntax into ng-repeat should work as well.

    <div ng-repeat="item in getItems() | filter:values" 
         ng-repeat-empty="'some-template.html'">
    </div>
    
  8. matsko commented on Feb 3, 2014

    @matsko
    ContributorAuthor

    @eternalmatt the sibling element solution can't be done. Anything that requires an element to be positioned close by another element is too strict. Otherwise you would have to pass the same ngRepeat expression into the HTML attribute.

    I like the last example, but it forces you to make an external template. The 2nd example is the best of both worlds, but it also requires strict positioning.

  9. caitp commented on Feb 3, 2014

    @caitp
    Contributor

    Why would the middle option require strict positioning? It would basically be transclude + filter out elements which don't contain the ng-repeat-empty attribute.

    But the third example would be the easiest to implement, and maybe that's a good starting point?

  10. matsko commented on Feb 3, 2014

    @matsko
    ContributorAuthor

    By strict I meant that the <div ng-repeat-empty> must be inside of the repeater. But that's no different than ng-switch. The last example can be done without breaking anything and is easier to read. Let's do that.

  11. eternalmatt commented on Feb 3, 2014

    @eternalmatt

    With the sibling element example, I was thinking this was feasible since
    ngRepeatStart and ngRepeatEnd both exist.
    On Feb 3, 2014 5:32 PM, "Matias Niemelä" notifications@github.com wrote:

    By strict I meant that the
    must be inside of the repeater. But that's no different than ng-switch.
    The last example can be done without breaking anything and is easier to
    read. Let's do that.

    Reply to this email directly or view it on GitHubhttps://github.com//issues/5919#issuecomment-34008554
    .

  12. mb-dev commented on Feb 4, 2014

    @mb-dev

    Syntax similar to ng-switch is also an option:

    <div ng-repeat="item in getItems() | filter:values">
        <div ng-repeat-content>
            .. HTML elements go here
        </div>
        <div ng-repeat-empty="'some-template.html'"></div>
    </div>
    

    Also can ng-repeat possibly add variable called empty and then it can be used like:

    <div ng-if="$empty" ng-include="'some-template.html'"></div>
    
  13. rodyhaddad commented on Feb 6, 2014

    @rodyhaddad
    Contributor

    @matsko
    Keep in mind that ng-repeat repeats the element it's on, not its content.
    So given:

    <ul>
      <li ng-repeat="item in list">{{ item }}</li>
    </ul>

    using your strict ng-repeat-empty would result in invalid html:

    <ul>
      <li ng-repeat="item in list">
        {{ item }}
        <li ng-repeat-empty>No items</li>
      </li>
    </ul>

    Same thing with <tr>, <td>, <option>, etc.
    So having the ng-repeat-empty inside the current ng-repeat would cause DOM parsing problems.

    What @mb-dev proposes would solve this, but it does create new issues to handle (ng-repeat would need to figure out that a -content is in use, or if one uses -empty without -content)

    We can always create a new repeater directive

    <ul ng-repeat-content="item in list">
      <li>{{ item }}</li>
      <li ng-repeat-empty>No items</li>
    </ul>

    I'm sure we can figure out a way to have both ng-repeat and ng-repeat-content share the same repeat-logic code, but different configuration for what to repeat and where.

  14. caitp commented on Feb 6, 2014

    @caitp
    Contributor

    The difficulty with these strategies is, we want the ng-repeat-empty directive to remove itself from the transcluded content, and give ngRepeat the transclude function.

    This doesn't really work with the current compiler design, and it would need to be hacked to support that. The simplest way to make this work would be with just an empty-template-url attribute, because this wouldn't depend on any changes to the compiler.

  15. tdakhla commented on Feb 7, 2014

    @tdakhla

    I totally ripped off someone's Plunkr example just now, but here's sorta what we've been doing to solve this issue:

    http://plnkr.co/edit/Ro3VnZdJbdX7yEKtqPOe?p=preview

    It's not the prettiest solution of the bunch, but all that's needed is one global isEmpty function to check the newly created scope variable. And that function most likely exists on scope in one way or another in a large app anyways. No extracting complex calculations into a controller, no extra directives/attributes or any of that.

  16. 13 remaining items

  17. urish commented on Jun 10, 2014

    @urish

    @esakal thanks for working on this!

    I suggest the following syntax, which is both backward-compatible, does not require giving a specific name to each instance of ng-repeat-empty, and I believe does not break the DOM parser in any way:

    <ul ng-repeat-container>
        <li ng-repeat="item in list">{{item.text}}</li>
        <li ng-repeat-empty><span class="error">No items found.</span></li>
    </ul>

    This syntax can also co-exists with the syntax mentioned by @matsko above.

    @IgorMinar any comments on the various syntax options suggested so far?

  18. rodyhaddad commented on Jul 1, 2014

    @rodyhaddad
    Contributor

    How about just:

    <div ng-repeat="item in getItems() as repeated_items">...</div>
    

    then you can have ngIf to display text for the zero case and you can also use the alias inside of the repeater to display stuff like "1 of 3" when the getItems returns a collection with a length that you don't otherwise know.

  19. matsko commented on Jul 1, 2014

    @matsko
    ContributorAuthor

    @rodyhaddad but that aliased value would be registered on the same scope that getItems() lives on.

    I really like this solution since it gives the most flexibility.

    How does everyone else feel here about this?

  20. IgorMinar commented on Jul 2, 2014

    @IgorMinar
    Contributor

    +1

  21. added 2 commits that reference this issue on Jul 2, 2014
    6b99f79
    7ae8129
  22. added a commit that references this issue on Jul 21, 2014
    e154f86
  23. danijar commented on Jul 22, 2014

    @danijar

    +1

  24. precz commented on Jul 29, 2014

    @precz

    +1

  25. joshbowdoin commented on Jul 29, 2014

    @joshbowdoin
    Contributor

    +1 ! - was hacking my way around this before (often filtering in controller on demand, so that I would have result of filtered list).

  26. huguangju commented on Sep 2, 2015

    @huguangju

    +1

  27. yuyudhan commented on Jan 7, 2018

    @yuyudhan

    +1

  28. Narretz commented on Jan 8, 2018

    @Narretz
    Contributor

    There's already a solution for this: you can use the alias expression as <something>, which will publish <something> into the scope, and then you can check if <something> has no length, and display something in that case.
    This is in the docs: https://code.angularjs.org/1.6.8/docs/api/ng/directive/ngRepeat
    See the example.

  29. locked as resolved and limited conversation to collaborators on Jan 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions