Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using *ngFor in CSS Grid Layout Undesirably Displaying Everything in One Column

I am new to programming and web dev in general. I am in the process of making a personal website.

I want to put boxes in a grid with 4 columns, similar to what you see here: https://devpost.com/software/search?query=is%3Afeatured

Each of these boxes represents an object, and I want to be able to display some of the data into the box, and the rest of data in a popup dialog when you click on the box.

I have been playing around with the CSS grid layout, which is becoming popular these days (highly recommend this video: https://www.youtube.com/watch?v=7kVeCqQCxlk).

The thing with 4 columns work when I hardcode bunch of div elements inside a wrapper div. However, whenever I use *ngFor on the wrapper containing my array of data and feed in the data from each iteration to an inner div element, the grid layout gets destroyed, placing everything into one column.

When I manually feed in the data using multiple div elements (item2 here), it works as desired:

.wrapper {
    margin-left: 1em;
    margin-right: 1em;
    display: grid;
    grid-template-rows: auto;
    grid-template-columns: repeat(4, 1fr);
    grid-row-gap: 1em;
    grid-column-gap: 1em;
}
<div class="wrapper">

    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
    </div>

    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
    </div>

    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
    </div>

    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
    </div>

    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
    </div>
    
</div>

Using *ngFor... it runs for the length of the array "stickyNotes," but stacks the boxes only below each row.

<div class="wrapper" *ngFor="let s of stickyNotes; let i = index" >
    <div class="item2" style="background-color: deepskyblue;">
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
        <p>{{s.title}}</p>
    </div>
</div>

My hack around this was... have this div element 4 times with i incrementing by 1 on *ngIf (i+1, i+2, etc.). Problem here is that when a new row starts, grid-row-gap in CSS is ignored.

<div class="item2" style="background-color: deepskyblue;" *ngIf="i < stickyNotes.length && i%4 === 0">
      <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
      <p>more text here.</p>
      <p>{{stickyNotes[i].title}}</p>
</div>

<div class="item2" style="background-color: deepskyblue;" *ngIf="i+1 < stickyNotes.length && i%4 === 0">
      <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
      <p>more text here.</p>
      <p>{{stickyNotes[i+1].title}}</p>
</div>

<!--2 more times until i+3-->

I am wondering if there is a way to create a custom iterable directive without destroying the grid property and without having the item2 class coded 4 times (or any other methods).

I have tried bootstrap row and col attributes, but with 4 columns, the responsiveness wasn't as nice as the grid layout.

Any help or suggestion would be appreciated!

like image 658
Chaewon Min Avatar asked Sep 12 '17 16:09

Chaewon Min


People also ask

How to use ngfor with CSS grid effectively?

This is how you would use ngFor with CSS grid effectively. You can use ng-container to prevent *ngFor from inserting divs into the dom. This will prevent generating a new grid with each loop through. The Angular ng-container is a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM.

How to prevent *ngfor from creating a new grid every loop?

You can use ng-container to prevent *ngFor from inserting divs into the dom. This will prevent generating a new grid with each loop through. The Angular ng-container is a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM. Then surround ng-container with a div with display: grid;.

What is CSS grid layout module?

The CSS Grid Layout Module offers a grid-based layout system, with rows and columns, making it easier to design web pages without having to use floats and positioning. The grid properties are supported in all modern browsers. A grid layout consists of a parent element, with one or more child elements.

What are grid properties in HTML?

The grid properties are supported in all modern browsers. A grid layout consists of a parent element, with one or more child elements. An HTML element becomes a grid container when its display property is set to grid or inline-grid.


1 Answers

*ngFor repeats the element you add it to. Simply put it on div.item2 and it should work:

<div class="wrapper" >
    <div class="item2" *ngFor="let s of stickyNotes; let i = index" style="background-color: deepskyblue;" >
        <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
        <p>more text here.</p>
        <p>{{s.title}}</p>
   </div>
</div>

The answer provided by Vega will work too, but the <ng-container> is not required(as long as you don't put another structural directive on the div.item2element), because asterisk is only syntactic sugar and expands to something similar:

<div class="wrapper" >
  <ng-template ngFor let-s [ngForOf]="stickyNotes" let-i="index">
    <div class="item2" style="background-color: deepskyblue;">
      <img class="img-responsive img-rounded" src="assets/Logo/square_filler.png" alt="pic-test" >
      <p>more text here.</p>
      <p>{{s.title}}</p>
    </div>
  </ng-template>
</div>

The reason behind the limit of one structural directive per element is given here:

Someday you'll want to repeat a block of HTML but only when a particular condition is true. You'll try to put both an *ngFor and an *ngIf on the same host element. Angular won't let you. You may apply only one structural directive to an element.

The reason is simplicity. Structural directives can do complex things with the host element and its descendents. When two directives lay claim to the same host element, which one takes precedence? Which should go first, the NgIf or the NgFor? Can the NgIf cancel the effect of the NgFor? If so (and it seems like it should be so), how should Angular generalize the ability to cancel for other structural directives?

There are no easy answers to these questions. Prohibiting multiple structural directives makes them moot. There's an easy solution for this use case: put the *ngIf on a container element that wraps the *ngFor element. One or both elements can be an ng-container so you don't have to introduce extra levels of HTML.

like image 51
Riiverside Avatar answered Oct 13 '22 21:10

Riiverside