Dans cet article, je vous montre comment utiliser Angular Material et NGRX pour avoir un tableau dynamique comme celui sur mon portfolio en ligne weroamba.com.

Angular Material vise à fournir aux développeurs le meilleur design UI/UX qu’ils puissent imaginer. Il est responsive et adapté aux mobiles, ce qui permet de construire des applications et des pages Web attrayantes en respectant les principes de conception Web modernes.

NgRx Store fournit une gestion d'état réactive pour les applications angulaires inspirées de Redux.

Il offre plusieurs avantages en simplifiant l'état de votre application en objets simples, en appliquant un flux de données unidirectionnel

Pour cet exemple j’utilise la version 11 d’Angular

0:00
/

Nous allons d’abord installer nos packages de NGRX :

ng add @ngrx/store

ng add @ngrx/effects

ng add @ngrx/store-devtools

Dans notre dossier shared

Nous créons un dossier store et nous ajoutons les fichiers suivants:

Dans notre fichier certifications.actions.ts

Nous ajoutons l’élément suivant:

import { Action } from "@ngrx/store";
 
export const GET_CERTIFICATIONS_REQUEST = '[GET_ALL] Certifications Request';
export const GET_CERTIFICATIONS_SUCCESS = '[GET_ALL] Certifications Success';
export const GET_CERTIFICATIONS_ERROR = '[GET_ALL] Certifications Error';
 
/****************************************
 * GET all certifications
 ****************************************/
export class GetAllCertificationsRequest implements Action {
  readonly type = GET_CERTIFICATIONS_REQUEST;
  constructor(public payload?: string) {
  }
}
 
export class GetAllCertificationsSuccess implements Action {
  readonly type = GET_CERTIFICATIONS_SUCCESS;
 
  constructor(public payload: any[]) {
  }
}
 
export class GetAllCertificationsError implements Action {
  readonly type = GET_CERTIFICATIONS_ERROR;
 
  constructor(public payload: Error) {
  }
}

Dans notre fichier certifications.reducers.ts nous ajoutons le code suivant:

import { createFeatureSelector, createSelector } from '@ngrx/store';
import * as certificationActions from './certifications.actions';
 
export interface State {
    data: any;
    action: string;
    done: boolean;
    error?: Error;
  }
 
  const initialState: State = {
    data: [],
    action: null,
    done: false,
    error: null
  };
 
  export function reducer(state = initialState, action: {type: string;
    payload?: any;}): State {
    // ...state create immutable state object
    switch (action.type) {
      /*************************
       * GET all certifications actions
       ************************/
      case certificationActions.GET_CERTIFICATIONS_REQUEST:
        return {...state, action: certificationActions.GET_CERTIFICATIONS_REQUEST, done: false};
      case certificationActions.GET_CERTIFICATIONS_SUCCESS:
        return {...state, data: action.payload, done: true};
      case certificationActions.GET_CERTIFICATIONS_ERROR:
        return {...state, done: true, error: action.payload};
    }
    return state;
  }
 
  /*************************
   * SELECTORS
   ************************/
  export const getCertificationsState = createFeatureSelector<State>('certifications');
  export const getAllCertifications = createSelector(getCertificationsState, (state: State) => state.data);
 
 

Dans notre fichier certifications.effects.ts nous ajoutons aussi le code suivant

import { Injectable } from "@angular/core";
import { Actions, Effect, ofType } from "@ngrx/effects";
import { Action } from "@ngrx/store";
import { Observable } from "rxjs";
import * as certificationActions from './certifications.actions';
 
import { catchError, map, switchMap } from "rxjs/operators";
import { CertificationService } from "../service/certification.service";
import { GetAllCertificationsError, GetAllCertificationsSuccess } from './certifications.actions';
 
@Injectable()
export class CertificationEffects {
  constructor(private actions$: Actions, private certificationService: CertificationService) {
  }
 
  @Effect()
  getAllCertifications$: Observable<Action> = this
    .actions$.pipe(
      ofType(certificationActions.GET_CERTIFICATIONS_REQUEST),
      map((action: certificationActions.GetAllCertificationsRequest) => action.payload),
      switchMap((queryParams) => this.certificationService.getAllCertifications(queryParams)),
      map(result => new GetAllCertificationsSuccess(result)),
      catchError((err) => [new GetAllCertificationsError(err)])
    );
}

Dans notre module principal nous ajoutons les éléments suivants:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterModule } from '@angular/router';
import { AppRoutingModule } from './app.routing';
 
import { AppComponent } from './app.component';
import { NavbarComponent } from './shared/navbar/navbar.component';
import { FooterComponent } from './shared/footer/footer.component';
 
import { ComponentsModule } from './components/components.module';
import { ExamplesModule } from './examples/examples.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
 
 
import {
  PERFECT_SCROLLBAR_CONFIG,
  PerfectScrollbarConfigInterface
} from 'ngx-perfect-scrollbar';
import { PdfViewerModule } from 'ng2-pdf-viewer';
import { ActionReducerMap, StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';
import * as  certificationsReducer  from './shared/store/certifications.reducers';
import { CertificationEffects}  from './shared/store/certifications.effects';
 
 
const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
  wheelSpeed: 0.5,
  swipeEasing: true,
  minScrollbarLength: 40,
  maxScrollbarLength: 300
};
 
export const reducers: ActionReducerMap<any> = {
  certifications: certificationsReducer.reducer,
 
};
 
 
@NgModule({
  declarations: [
    AppComponent,
    NavbarComponent,
    FooterComponent
  ],
  imports: [
    BrowserModule,
    NgbModule,
    FormsModule,
    RouterModule,
    ComponentsModule,
    ExamplesModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    PerfectScrollbarModule,
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([CertificationEffects]),
    StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }),
 
  ],
  providers: [
    {
      provide: PERFECT_SCROLLBAR_CONFIG,
      useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG
    },
 
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
 

Affichons maintenant les données dans notre tableau angular material table dynamique avec possibilité expandable

Dans notre fichier navigation.ts

import { trigger } from '@angular/animations';
import { SelectionModel } from '@angular/cdk/collections';
import {  ChangeDetectorRef, ViewEncapsulation } from '@angular/core';
import { ViewChild } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { state, style, transition, animate } from '@angular/animations';
import { select, Store } from '@ngrx/store';
import * as fromCertification from 'app/shared/store/certifications.reducers';
import { getAllCertifications } from '../../shared/store/certifications.reducers';
import { GetAllCertificationsRequest } from 'app/shared/store/certifications.actions';
 
@Component({
    selector: 'app-navigation',
    templateUrl: './navigation.component.html',
    styleUrls: ['./navigation.component.scss'],
    encapsulation: ViewEncapsulation.None,
    animations: [
        trigger('detailExpand', [
            state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),
            state('expanded', style({ height: '*', display: 'block' })),
            transition('collapsed=>expanded', animate('225ms cubic-bezier(0.2, 0.6, 0.2, 1)')),
            transition('expanded=>collapsed', animate('20ms cubic-bezier(0.2, 0.0, 0.4, 1)'))]),
    ],
})
export class NavigationComponent implements OnInit {
 
    displayedColumns = [
        'name',
        'plateform',
        'validate',
    ];
 
    displayedColumnsTwo = [
        'file',
    ];
 
    expandedElement: any | null;
    @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
    @ViewChild('sort1', { static: true }) sort: MatSort;
 
    selection = new SelectionModel<any>(true, []);
    customersResult: any[] = [];
    dataSource = new MatTableDataSource([]);
    dataSource2 = new MatTableDataSource([""]);
 
    onLoading = true;
 
    dataItems: any;
    pageSizeOptions = [10, 25, 50];
    queryParams: string;
    totalData: any;
 
 
    constructor(private store: Store<{certification:fromCertification.State}>) {
        this.getCertifications();
    }
 
    ngOnInit() {
 
        if (this.paginator) {
            this.paginator.page.subscribe((data) => {
                this.queryParams = `pageSize=${data.pageSize}&page=${data.pageIndex + 1}`;
                this.getCertifications(this.queryParams);
             
            });
        }
 
    }
 
    public getCertifications(queryParams?:string){
        this.store.dispatch(new GetAllCertificationsRequest(queryParams));
        this.store.select(getAllCertifications).subscribe(data=>{
            this.onLoading = false;
            this.dataItems =data;
         
            this.dataSource = new MatTableDataSource( this.dataItems.data);
            if ( this.dataItems.data &&  this.dataItems.data.length) {
                this.totalData =  this.dataItems.data.length;
            }
        });
    }
 
 
}
 

Et enfin dans notre fichier navigation.component.html

Nous avons le code suivant:

<perfect-scrollbar style="height: 120vh">
  <div class="loading-list" *ngIf="onLoading">
    <span
      *ngIf="onLoading"
      class="spinner-grow spinner-grow-md spinner-primary"
      role="status"
      aria-hidden="true"
    ></span>
    <span
      *ngIf="onLoading"
      class="spinner-grow spinner-grow-md spinner-secondary"
      role="status"
      aria-hidden="true"
    ></span>
  </div>
 
 
  <table
    mat-table
    multiTemplateDataRows
    class="table table-striped"
    #table
    [dataSource]="dataSource"
    matSort
    sort="matSort"
  >
    <ng-container matColumnDef="name">
      <th
        mat-header-cell
        *matHeaderCellDef
        class="custom-color"
        style="color: black; font-size: 14px"
      >
        <h6>Cours</h6>
      </th>
      <td mat-cell *matCellDef="let certification">
        {{ certification.name }}
        <button
          mat-icon-button
          (click)="expandedElement = expandedElement === certification ? null : certification"
        >
          <ng-container
            *ngIf="expandedElement === certification; else noExpandedElement"
          >
            <mat-icon>expand_less</mat-icon>
          </ng-container>
          <ng-template #noExpandedElement>
            <mat-icon>expand_more</mat-icon>
          </ng-template>
        </button>
      </td>
    </ng-container>
 
    <ng-container matColumnDef="plateform">
      <th
        mat-header-cell
        *matHeaderCellDef
        class="custom-color"
        style="color: black !important; font-size: 14px"
      >
        <h6>Plateforme</h6>
      </th>
      <td mat-cell *matCellDef="let certification">{{ certification.plateform_name }}</td>
    </ng-container>
 
    <ng-container matColumnDef="validate">
      <th
        mat-header-cell
        *matHeaderCellDef
        style="color: black !important; font-size: 14px"
      >
        <h6>Periode</h6>
      </th>
      <td mat-cell *matCellDef="let certification">{{ certification.validate_date }}</td>
    </ng-container>
 
    <ng-container matColumnDef="expandedDetail">
      <td
        mat-cell
        *matCellDef="let element"
        [attr.colspan]="displayedColumns.length"
      >
        <div
          [@detailExpand]="
            element == expandedElement ? 'expanded' : 'collapsed'
          "
          style="position: relative"
         
        >
          <div class="loading-child-list" *ngIf="onLoadingChild">
            <mat-spinner *ngIf="onLoadingChild"></mat-spinner>
          </div>
 
          <table
            class="table table-striped"
            mat-table
            #table
            [dataSource]="dataSource2"
          >
            <!-- firstName  -->
            <ng-container matColumnDef="file">
              <th
                mat-header-cell
                *matHeaderCellDef
                class="font-sm mat-cell"
                style="font-size: 14px"
              >
                <h6 style="font-size: 15">Certificat</h6>
              </th>
              <td mat-cell *matCellDef="let certification">
             
                 <pdf-viewer
                  [src]="element.img"
                  [original-size]="false"
                ></pdf-viewer>
              </td>
            </ng-container>
 
            <tr
              mat-header-row
              *matHeaderRowDef="displayedColumnsTwo; sticky: true"
            ></tr>
            <tr
              mat-row
              *matRowDef="let row; columns: displayedColumnsTwo"
            ></tr>
          </table>
        </div>
      </td>
    </ng-container>
 
    <tbody>
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr
        mat-row
        *matRowDef="let element; columns: displayedColumns"
        class="example-element-row"
        [class.example-expanded-row]="expandedElement === element"
      ></tr>
      <tr
        mat-row
        *matRowDef="let row; columns: ['expandedDetail']"
        class="example-detail-row"
      ></tr>
 
      <!-- <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
 
      <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> -->
    </tbody>
  </table>
 
  <div class="mat-table__message" *ngIf="onLoading">
    Veuillez patienter chargement en cours ...
  </div>
  <div
    class="mat-table__message"
    style="
      padding: 30px !important;
      text-align: center;
      font-weight: 500;
      color: #006699;
    "
    *ngIf="onLoading"
  >
    <img
      height="100"
      width="100"
      style="height: 100px; width: 100px"
      src="/assets/img/file-searching.svg"
      alt="Not found"
    />
    <h5></h5>
  </div>
</perfect-scrollbar>
 
<!-- start: BOTTOM -->
<!-- start: BOTTOM -->
<div class="mat-table__bottom">
  <mat-spinner [diameter]="25" *ngIf="onLoading"></mat-spinner>
  <mat-paginator
    [pageSize]="dataItems?.pageSize"
    [length]="dataItems?.total"
    [pageSizeOptions]="pageSizeOptions"
    [showFirstLastButtons]="true"
  ></mat-paginator>
</div>


Ajoutons  l’élément suivant dans le css du component:

pdf-viewer {
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(5px 5px 5px #222222);
  height: 400px;
  width: 500px;
  margin: 0 auto;
}

Nous avons terminé .Vous pouvez aussi vous inspirer et créer un tableau expandable avec Angular.