Angular CDK: Context Menu personalizado

por | Ene 9, 2023 | Angular, Angular CDK, Angular Material | 0 Comentarios

Angular CDK Context Menu

Context Menu o Menú Contextual

Es posible que alguna vez te hayas preguntado si el menú con opciones que sale cuando haces click derecho en algún lugar de una web se puede personalizar. Y la respuesta es .

Desde la llegada de la versión 14 de Angular tenemos disponible el módulo Menu en el CDK. Éste nos permitirá crear una multitud de diferentes de menús, y entre ellos, el menú contextual.

Tecnologías

El stack de versiones que se usarán para el desarrollo del proyecto es el siguiente.

  • Angular CLI (15.0.0)
  • Node (16.18.1)
  • Npm (9.1.2)
  • Typescript (4.8.4)

Objetivo

Vamos a crear una aplicación sencilla con unos cards de ejemplo, en los cuales el botón derecho mostrará un menú contextual personalizado.

    Creando la aplicación

    Abrimos el terminal y ejecutamos el siguiente comando para crear una aplicación básica en Angular.

      ng new custom-context-menu --minimal true --style scss --routing false

      Seguidamente añadimos Angular Material a nuestro proyecto.

        ng add @angular/material

        Creamos un componente sencillo de un dashboard a partir de una plantilla de Schematics.

          ng generate @angular/material:dashboard components/dashboard

          Ahora vamos a cambiar el template del componente app.component.ts para que muestre el nuevo componente.

          app.component.ts

          import { Component } from '@angular/core';
           
          @Component({
            selector: 'app-root',
            template: `
              <app-dashboard></app-dashboard>
            `,
            styles: []
          })
          export class AppComponent {
            title = 'custom-context-menu';
          }
          

          Podemos compilar la aplicación para ver cómo lo llevamos hasta ahora.

          ng serve -o
          Angular CDK Context Menu - Basic App

          Implementando el Context Menu

          Para usar la directiva que se encarga de que podamos implementar un menú contextual personalizado tenemos que importar el módulo CdkMenuModule en el archivo app.module.ts.

            app.module.ts

            import { CdkMenuModule } from '@angular/cdk/menu';
             
            @NgModule({
              declarations: [ ... ],
              imports: [
                ...
                CdkMenuModule
              ],
              providers: [],
              bootstrap: [ ... ]
            })
            export class AppModule { }

            Ahora implementaremos la directiva cdkContextMenuTriggerFor en los mat-card del componente app-dashboard generado anteriormente.

            Para ello crearemos un menú sencillo con 3 opciones dentro de un ng-template. Le daremos estilo. Y crearemos unas funciones de ejemplo para comprobar que los botones del menú funcionan.

            dashboard.component.ts

            @Component({
              selector: 'app-dashboard',
              template: `
                <div class="grid-container">
                  <h1 class="mat-h1">Dashboard</h1>
                  <mat-grid-list cols="2" rowHeight="350px">
                    <mat-grid-tile *ngFor="let card of cards | async" [colspan]="card.cols" [rowspan]="card.rows">
                      <mat-card class="dashboard-card" [cdkContextMenuTriggerFor]="context_menu">
                        ...
                      </mat-card>
                      
                      <!-- Context Menu -->
                      <ng-template #context_menu>
                        <div class="context-menu mat-elevation-z8" cdkMenu>
                          <button class="context-menu-item" cdkMenuItem (click)="cut()">Cut</button>
                          <button class="context-menu-item" cdkMenuItem (click)="copy()">Copy</button>
                          <button class="context-menu-item" cdkMenuItem (click)="link()">Link</button>
                        </div>
                      </ng-template>
                      
                    </mat-grid-tile>
                  </mat-grid-list>
                </div>
              `,
              styles: [`
                ...
            
                /* Context Menu */
                .context-menu {
                  display: inline-flex;
                  flex-direction: column;
                  min-width: 180px;
                  max-width: 280px;
                  background-color: rgb(255, 255, 255);
                  padding: 6px 0;
                  border-radius: 4px;
                }
            
                .context-menu-item {
                  background-color: transparent;
                  cursor: pointer;
                  border: none;
            
                  user-select: none;
                  min-width: 64px;
                  line-height: 36px;
                  padding: 0 16px;
            
                  display: flex;
                  align-items: center;
                  flex-direction: row;
                  flex: 1;
                }
            
                .context-menu-item:hover {
                  background-color: rgb(208, 208, 208);
                }
            
                .context-menu-item:active {
                  background-color: rgb(170, 170, 170);
                }
              `]
            })
            export class DashboardComponent {
              ...
             
              constructor(private breakpointObserver: BreakpointObserver) {}
             
              /**
               * Do whatever you want
               */
              cut(): void {
                console.log('Acción de cortar');
              }
            
              /**
               * Do whatever you want
               */
              copy(): void {
                console.log('Acción de copiar');
              }
            
              /**
               * Do whatever you want
               */
              link(): void {
                console.log('Acción de ir a un enlace');
              }
            }
            

            Resultado

            Llegados a este punto, si compilamos el proyecto ejecutando el comando ng serve -o en la terminal se nos abrirá en el navegador y veremos el resultado.

            Ya podríamos comprobar cómo al hacer click derecho encima de los cards nos abre el menú que hemos creado. Y si, por el contrario, hacemos click derecho entre los cards o en otro sitio, vemos el menú contextual por defecto. Además, en la consola vemos el texto que imprimen los console.log de nuestros botones.

            Angular CDK Context Menu - Implemented

            Stackblitz

            A continuación, tenéis el proyecto en Stackblitz para poder toquetear y hacer pruebas en vivo con él 😉