change the number of columns in mat-grid-list when the component first loads

Viewed 930

I am implementing a mat-grid-list of images and I am changing the number of columns dynamically based on the screen size. This works great except when it first loads it is set to 3 columns regardless of the screen size, until an event happens. So if you navigate to a different component and come back you will once again see 3 columns. I tried adding some code to the constructor or other lifecycle hooks without success but it might just be me not implementing it correctly. On the typescript file, the products array of objects, the constructor, and the ngOnInit() hook are not relevant. The first image is what it looks like when it loads because it defaults to 3 and the second image is what it looks like when I click something or resize the screen, this second image is what it should look like. You will notice that on the typescript file I have the default number of rows set to 3 but if I remove it I get an error. Any feedback on the design is also welcomed.

 <!-- html -->
<div #gridView>
    <mat-grid-list cols="{{columnNum}}" gutterSize="5rem" rowHeight="25rem">
        <mat-grid-tile *ngFor="let product of products">
            <img src="{{ product.image }}" alt="">
        </mat-grid-tile>
    </mat-grid-list>
</div>

typescript file

import { AfterViewInit, Component, HostListener, OnInit, ViewChild } from '@angular/core';
import { ProductsService } from '../services/products.service';

@Component({
  selector: 'app-gallery',
  templateUrl: './gallery.component.html',
  styleUrls: ['./gallery.component.css']
})
export class GalleryComponent implements OnInit, AfterViewInit {
  products : {name: string, productId: string, image: string, price: number, desc: string, size: string, isPrintAvailable: boolean}[] = [];
  @ViewChild('gridView') gridView: any;
  columnNum = 3; //initial count
  tileSize = 450; //one tile should have this width

  setColNum(){
     let width = this.gridView.nativeElement.offsetWidth;
     this.columnNum = Math.trunc(width/this.tileSize);
   }

   //calculating upon loading
   ngAfterViewInit() {
     this.setColNum();
   }

   //recalculating upon browser window resize
   @HostListener('window:resize', ['$event'])
   onResize() {
     this.setColNum();
   }

  constructor(private productService: ProductsService) {}

  ngOnInit(): void {
    this.products = this.productService.getProducts();
  }

}

what it looks like when it loads enter image description here

what it looks like after an event like a click or resize the screen (If I navigate away and back it goes back to the top image) enter image description here

2 Answers

After reading page after page I found a solution which does not require messing with the typescript file and works perfectly. Basically you create a directive to replace the cols property and the directive does the rest. I'll add the code for the mat-grid-list tag as well as the code for the directive but here's the page so that I can give credit to the author https://www.mymiller.name/wordpress/programming/angular-material-grid-layout-responsive/

html

<mat-grid-list [gridCols]="{xs: 1, sm: 2, md: 3, lg: 4, xl: 5}">

directive

import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Directive, Input, OnInit } from '@angular/core';
import { MatGridList } from '@angular/material/grid-list';

export interface GridColumns {
  xs: number;
  sm: number;
  md: number;
  lg: number;
  xl: number;
}
@Directive({
  selector: '[gridCols]'
})
export class GridColsDirective implements OnInit {
  private gridCols: GridColumns = {xs: 1, sm: 2, md: 4, lg: 6, xl: 8};

  public get cols(): GridColumns {
    return this.gridCols;
  }

  @Input('gridCols')
  public set cols(map: GridColumns) {
    if (map && ('object' === (typeof map))) {
      this.gridCols = map;
    }
  }

  public constructor(private grid: MatGridList, private breakpointObserver: BreakpointObserver) {
    if(this.grid != null) {
      this.grid.cols = this.gridCols.md;
    }
  }

  public ngOnInit(): void {
    if(this.grid != null) {
      this.grid.cols = this.gridCols.md;
    }
    this.breakpointObserver.observe([
      Breakpoints.XSmall,
      Breakpoints.Small,
      Breakpoints.Medium,
      Breakpoints.Large,
      Breakpoints.XLarge
    ]).subscribe(result => {

      if (result.breakpoints[Breakpoints.XSmall]) {
        this.grid.cols = this.gridCols.xs;
      }
      if (result.breakpoints[Breakpoints.Small]) {
        this.grid.cols = this.gridCols.sm;
      }
      if (result.breakpoints[Breakpoints.Medium]) {
        this.grid.cols = this.gridCols.md;
      }
      if (result.breakpoints[Breakpoints.Large]) {
        this.grid.cols = this.gridCols.lg;
      }
      if (result.breakpoints[Breakpoints.XLarge]) {
        this.grid.cols = this.gridCols.xl;
      }
    });
  }
}

The problem was first you got columnNum as 3 then it will change according to your window size. the problem is if you change your size immediately you got ExpressionChangedAfterItHasBeenCheckedError so you need some time for columnNum change there are some ways to fix this problem one of the ways is using the set timeout function or using cdr.detectChanges()

setColNum() {
    let width = this.gridView.nativeElement.offsetWidth;
    setTimeout(() => {
      this.columnNum = Math.trunc(width / this.tileSize);
    }, 0);
  }

or

constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
    this.setColNum();
    this.cdr.detectChanges();
  }
Related