Tutorial Blog: How to Use Bootstrap Span Elements with ngRepeat

This tutorial is too simple to be a tutorial article. I place it here in the post section.

Introduction

This will be a short tutorial. I find that a bug in an application to be the best motivation for finding a solution. Recently I had a very small problem, which appeared to be unsolvable first. When a solution became available, it seems to be so simple that it shouldn't even be a problem in a first place. Anyways, I think this problem and its solution, is worthy being documented. In my case, I couldn't find any solution online. It is either too simple or too unimportant to everyone else. In case someone else had the same problem, this solution can help.

So what is the problem? Imagine that you have a list of span elements, they are decorated by Bootstrap's label classes (label and label-info or label-success, label-warning, etc). When they are displayed on screen, they are displayed in one line. And when the page resizes, they are still showing as one line, and not properly self aligned in to multiple lines. Here is a screenshot of what this problem looks like:

Why is this problem happens? I admit that I was a bit naive. I was using ngRepeat (of AngularJS) on the span element directly. This is what I did. And I freely admit I didn't know better when I did this. I should have know better:

<div class="row">
   <div class="col-xs-12">
      <span class="label label-info" ng-repeat="item in vm.keywords">{{item}}</span>
   </div>
</div>

The HTML mark up, as you can see, looks reasonable. What I failed to realize is that with such design, the span element will be placed one after another, with no spaces between them. And the span is a inline element, such elements line up will cause issue like displayed in the screenshot above.

Finding a Solution Inside the Box

The first solution I tried is how do I add an extras space for the span element while ngRepeat directive is part of it. It is a really hard question to ask with Google. It is why so difficult to find a straight forward answer.

Turned out, there is one solution that hit the jackpot. And it was the exactly the right solution. yet, it was so illusive and this solution is hidden in the fifth page of the Google search results. Unfortunately, I can't find the page any more. Otherwise, I would give proper credit to that guy. Anyway, I didn't come up this answer. It is tricky, and I didn't know HTML5 enough to come up with something like this. The solution is to append a space character at the end of an element. When ngRepeat replicates the element in the loop, it will ensure the spacing between the element is properly managed. This is done with CSS.

With CSS and HTML5, you can specify content to be added after an HTML element. So, here is how the CSS class is defined in the solution I found:

.space::after {
   content: " ";
}

The CSS definition is pretty simple, for any element with the CSS class "space", at the end add a space character. The content keyword in CSS means the style to be added is text content. So anything enclosed in single quotes or double quotes is the text content to be added to the end of the element.

Here is how this new CSS class is used with ngRepeat in the solution:

<div class="row">
   <div class="col-xs-12">
      <span class="space" ng-repeat="item in vm.keywords">
         <span class="label label-info">{{item}}</span>
      </span>
   </div>
</div>

Here is a screenshot of the list of span elements all aligned correctly and breaking into the next lines:

The idea here is wrapping the span element inside another span element. The extra content space is added to the end of the outer span element. This ensures the proper spacing of the span element (the outer ones) when they are added to the DOM tree for the page.

This is solution #1, which taught me two things:

  • With CSS and HTML 5 it is easy to add extra content to the HTML page.
  • One can wrap a span element inside another span element.

The second point is something I should have realized, but failed to see it until it was presented to me as a solution. The first point is something I really didn't know. If I ignored this problem or simply put up a dummy solution and forget about it, I wouldn't learn about this. So every bug one attempts to fix, will teach us something we didn't know.

Now that I know I can wrap a span element inside another span element, is there another way I can fix this? It turned out, I don't have to use such a complicated solution described in this section, with a new CSS class to add an extra space somehow.

Finding a Solution Outside of the Box

The solution I am trying to come up with is to somehow add some spaces after each of the span elements, so that they don't "stick" to each other. The inside-the-box solution is somehow I add a space to each span element.

With the solution at hand. I was able to accomplish it. Yet, the solution is complex. Unless one knows how to use content keywords in CSS, there is no way of adding a space to the end of an element. And if someone else is looking at this implementation and they didn't know the intention of such design, it is likely some unintended changes would eventually break this implementation.

Is there a better way to fix the original problem? There is. Here is the implementation I come up at the end:

<div class="row">
   <div class="col-xs-12">
      <span ng-repeat="item in vm.keywords">
         <span class="label label-info">{{item}}</span>
      </span>
   </div>
</div>

Can you see the difference here? All I did is taking out the CSS class "space" of the outer span element. If you have guessed, you probably thought the space put it at the end is not needed. When the outer span is doing the replication with ngRepeat, the final DOM elements would be like this:

<div class="row">
   <div class="col-xs-12">
      <span ... >
         <span class="label label-info">{{item #1}}</span>
      </span>
      <span ... >
         <span class="label label-info">{{item #2}}</span>
      </span>
      <span ... >
         <span class="label label-info">{{item #3}}</span>
      </span>
      <span ... >
         <span class="label label-info">{{item #4}}</span>
      </span>
      <span ... >
         <span class="label label-info">{{item #5}}</span>
      </span>
      <span ... >
         <span class="label label-info">{{item #6}}</span>
      </span>
      ...
   </div>
</div>

With such DOM elements representation, there is enough spaces between each span elements. Hence, the extra space to be added with CSS class specification is not necessary at all. If I never get to the point of wrapping a span element with another span element. I would never be able to get to this conclusion. I feel this is a solution was outside the box in which I was trapped.

Here is a screenshot of all three ways of alignments:

The first is the problematic implementation. The second one is the solution with the CSS class. The last one is the one where I removed the CSS class and it still worked as expected. So the new CSS class is not needed after all.

Summary

This is it. It is a short tutorial. My objective for this tutorial is simple. For one I just want to show you that using ngRepeat and span without some wrapping is a bad idea. It can cause the problem where all the span elements are placed in a line. They will not re-align into multiple lines when the overall width of the page gets smaller. The second objective is to discuss how I learned use CSS and HTML 5 to add new text content. This could be useful for future projects. Lastly, the lesson I learned is that I can wrap a span inside another span element. This allows me to use the ngRepeat directive in the outer span element, and use the inner span element for the actual display decoration. Turned out the wrapper span implementation solves the issue without the need for more complex designs (using CSS to add space at the end of an HTML element).

If you never run into similar problem as I have, good for you, and if you did and have figured out the solution, I am very happy for you. But if you are one of those unfortunate, who encountered this issue, and have not found a solution yet, check it out. I hope it can give you what is needed. Good luck.


Add Comment

Comments