Adjust the column count in mat-grid-list upon the initial loading of the component

My goal is to implement a mat-grid-list of images with a dynamic number of columns based on the screen size. Everything works perfectly except for one small glitch – when the grid first loads, it defaults to 3 columns regardless of the screen size until an event triggers a change. So, if you navigate away and then come back, you'll see 3 columns once again. I've tried various approaches in the constructor and other lifecycle hooks, but so far without success. It's possible that I'm not implementing them correctly. The products array of objects, constructor, and ngOnInit() hook in the typescript file are not relevant to this issue. The first image shows how it looks upon loading with 3 columns as default, while the second image displays how it should look after clicking something or resizing the screen. Feel free to provide feedback on the design as well.

 <!-- 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 width

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

   //initial calculation
   ngAfterViewInit() {
     this.setColNum();
   }

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

  constructor(private productService: ProductsService) {}

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

}

Initial load appearance https://i.sstatic.net/lXDhZ.png

Appearance after triggering an event like a click or screen resize (resets to initial state when navigating away and back) https://i.sstatic.net/7KGuQ.png

Answer №1

After thorough research, I discovered a clever solution that eliminates the need to modify the typescript file and functions flawlessly. Essentially, you can create a directive to replace the cols property, and the directive takes care of the rest. Below, I will share the code for the mat-grid-list tag and the code for the directive. I want to acknowledge the original author of this helpful page:

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;
      }
    });
  }
}

Answer №2

Initially, the column number was set to 3, but it changes based on the window size. The issue arises when the size is changed too quickly, resulting in an ExpressionChangedAfterItHasBeenCheckedError. To resolve this problem, you can introduce a delay for the column number change. One approach is to use either the setTimeout function or cdr.detectChanges()

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

Alternatively,

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

HTML form submitting with a symbol illustration

I am currently facing an issue with my website. I would like to create a search button using the symbol from fontawesome (<i class="fas fa-search"></i>), but I am unsure how to replace the form submission button with this symbol in order to sub ...

Unable to showcase the compilation in PDF form

I have a link on my page that, when clicked by the user, retrieves a list from the database using an ajax call and displays it. Now, I'm looking to add another link that, when clicked, will fetch the list from the database via ajax and present it in ...

What could be causing my JSON product list to not load properly?

My list is not loading and I can't figure out why. I've included my json, jquery, and HTML below. The console isn't showing any errors, but the list is still blank. Any help would be greatly appreciated as I am new to working with json. Than ...

Angular 5 Error Messages for HTTP Interceptors

I'm facing an issue regarding Angular 5: HTTP Interceptors. I am still new to this, so please bear with me as I grasp the concepts. Here is the error message that I encountered: compiler.js:19514 Uncaught Error: Provider parse errors: Cannot instan ...

Strange behavior detected in TypeScript generic function when using a class as the generic parameter

class Class { } const f0 = <T extends typeof Class> (c:T): T => { return c } const call0 = f0 (Class) //ok const f1 = <T extends typeof Class> (c:T): T => { const a = new c() return a //TS2322: Type 'Class' is not assigna ...

Unit test does not show the PrimeNG menubar start directive

Currently, I am in the process of writing Jasmine tests for my component which includes PrimeNG's menubar. Within this component, I am utilizing the start template directive in the following manner: <p-menubar id='menubar' [model]='i ...

Encountered a problem while trying to install angular/cli via the

Encountering errors while attempting to install Angular/CLI using the npm command line. The error message displayed is as follows: npm ERR! Darwin 16.7.0 npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "-g" "@angular/cli" npm ERR! node ...

What is the best way to merge multiple *ngifs in Angular?

Hey there, I am looking to create a conditional statement with three different outputs that will be displayed in a table. Currently, this is the code snippet I have: <td><div *ngIf="todo.diffDays >90">ninety</div> </td> I want ...

Show an error notification if the mobile device does not meet the requirements for jquery mobile version

Currently, I am in the process of creating a website using HTML5, CSS3, and jquerymobile. My goal is to have an error message appear saying "Your mobile browser does not support this application" if the page is not rendering correctly or if the jquery mobi ...

Cobalt does not reflect changes in React components when the component's state is updated

I am currently developing a small React application for Cobalt, and so far everything is running smoothly. However, I have encountered an issue with rerendering a specific portion of HTML when the component's state changes. The layout consists of a me ...

Angular 2: Navigating through submenu items

I have a question about how to route submenu elements in Angular 2. The structure of my project is as follows: -app ---login ---registration ---mainApp (this is the main part of the app, with a static menu and links) -----subMenu1 (link to some con ...

Creating a PDF export of your grid using Kendo Grid is a straightforward

I've been facing a challenge while trying to export an entire page using Kendo Angular PDF. Everything works smoothly until I add a Kendo Angular Grid onto the page. The problem arises when certain rows go missing and extra blank space appears on some ...

Is there a way to place the icon on the right side of the @mui/Chip component?

Currently, I am working with MUI version 5.15.0. I have a component called Chip, and my goal is to display the icon after the label on the right side. I attempted to use the CSS rule - .MuiChip-icon{ order:1 }, but it resulted in too much spacing. Additio ...

"Exploring the interactivity of touch events on JavaScript

Hi there, I'm currently facing an issue with the touch events on my canvas. While the mouse events are functioning correctly and drawing as expected, incorporating touch events seems to be causing a problem. When I touch the canvas, the output remains ...

Unable to display label in form for Angular 2/4 FormControl within a FormGroup

I'm having trouble understanding how to: Use console.log to display a specific value Show a value in a label on an HTML page Display a value in an input text field Below is my TypeScript component with a new FormGroup and FormControls. this.tracke ...

I can't decide which one to choose, "ngx-bootstrap" or "@ng-bootstrap/ng-bootstrap."

Currently, I am in the process of deciding whether to use Bootstrap 4 with angular 4 for my upcoming project. However, I find myself torn between choosing npm install --save @ng-bootstrap/ng-bootstrap or npm install ngx-bootstrap --save. Could someone pl ...

Angular TimeTracker for tracking time spent on tasks

I need help creating a timer that starts counting from 0. Unfortunately, when I click the button to start the timer, it doesn't count properly. Can anyone assist me in figuring out why? How can I format this timer to display hours:minutes:seconds li ...

What is the best way to update setState when triggering an onChange event?

Here is the code snippet for a React component that handles cryptocurrency selection: import React, { Component } from 'react'; import { Select } from 'antd'; import { connect } from "react-redux"; class SelecionarCrypto extends Compo ...

When updating data with Angular Reactive Forms, the default value of input appears as 'undefined' upon submission, even though it is bound to the value property rather than the placeholder

I have developed a versatile Angular component that I am using to create various forms for the purpose of easily modifying multiple data items. Within this component, I have incorporated 2 input fields which are tied to input properties while iterating ov ...

Transform an Angular 2 application to seamlessly incorporate an SDK

I have been working on an Angular 2 application and I am curious if it is feasible to transform this into an SDK that can be easily integrated into other applications by simply adding script tags in their headers. If this conversion is not achievable, co ...