Gestire Template differenti per lo stesso componente in Angular (app mobile e dektop)

Ciao, in questo articolo ti vado a mostrare una soluzione che adotto quando devo creare un frontend in Angular che funzioni bene sia su piattaforme desktop che piattaforme mobile.

Non voglio soffermarmi sull’utilizzo dei CSS per rendere responsive i vari componenti o sull’utilizzo di griglie dinamiche per creare le varie pagine, ne ho già parlato in un precedente articolo.

1. Perché dovrei aver bisogno di gestire template differenti per lo stesso componente?

Domanda più che corretta, nell’esempio che vado a portare in questo articolo ti mostrerò la creazione di una toolbar che su desktop si presenta con un menù a voci sulla destra mentre per mobile a causa del poco spazio disponibile, il menù sarà contenuto in un pannello laterale che si potrà aprire cliccando una icona.

2. Come riconoscere il tipo di dispositivo che sta visitando la mia pagina per capire quale template mostrare?

Altra domanda molto sensata, ci sono diversi modi per ottenere le informazioni relative al dispositivo che sta utilizzando la nostra app, il mio preferito è utilizzare un package scaricabile da npm che si chiama “ngx-device-detector“. Puoi trovare la documentazione al seguente link:

https://www.npmjs.com/package/ngx-device-detector

Questa libreria mette a disposizione un servizio con tanti metodi utili tra cui isMobile(), isTablet() e isDesktop() che andremo ad utilizzare nel progetto.

3. Come funziona la strategia

Purtroppo per sua natura Angular non permette di cambiare dinamicamente i template dei componenti in quanto carica staticamente il file html definito nel decoratore prima di inizializzare il componente stesso.

Fatta questa piccola premessa ti vado a spiegare il ragionamento che sta dietro a quello che andremo poi a sviluppare.

Il tutto si basa sulla possibilità di estendere le classi Typescript dei componenti di Angular in modo da poter creare un componente generico che non conterrà quasi nulla di template ma che conterrà invece tutta la parte di logica del componente e poi creare dei sotto-componenti per mobile e desktop.

Rappresentazione grafica del progetto

Come puoi vedere dall’immagine andrò a creare un componente “toolbar” generico il quale conterrà tutte le proprietà ed i metodi necessari utili al funzionamento di una toolbar e nel suo template si occuperà di mostrare il relativo componente desktop o mobile basandosi su quanto restituito dai metodi isDesktop(), isTablet() e isMobile().

4. Via col codice

In questo articolo do un po’ per scontato che tu conosca almeno le basi di Angular e che comunque tu abbia una buona familiarità con il framework stesso. Se così non fosse ti consiglio di seguire il tutorial presente sul sito ufficiale di Angular.

https://angular.io/tutorial

Inizio con il creare un progetto normalissimo con il comando

ng new progetto-prova

Una volta generato il progetto vado a crearmi i componenti necessari ovvero le toolbar e un componente che mi rappresenta una sorta di homepage, lancio i seguenti comandi:

ng g c homepage
ng g c toolbar
cd toolbar
ng g c desktop-toolbar
ng g c mobile-toolbar

Finita la creazione dei componenti necessari possiamo passare a scrivere il codice del componente toolbar generico, parto incollando il file Typescript

import { Component, OnInit } from '@angular/core';
import { DeviceDetectorService } from 'ngx-device-detector';

@Component({
  selector: 'toolbar',
  templateUrl: './toolbar.component.html',
  styleUrls: ['./toolbar.component.scss']
})
export class ToolbarComponent implements OnInit {

  public title: string = "Titolo";
  public menu: string[] = ["Home", "Chi siamo", "Contatti"];

  constructor(
    public device: DeviceDetectorService
  ) { }

  ngOnInit(): void {
  }

}

Come puoi notare ho importato il DeviceDetectorService dalla libreria npm descritta in precedenza, dopodiché ho creato due proprietà: la prima rappresenta il titolo che voglio visualizzare nella mia toolbar, il secondo rappresenta le voci del menù da mostrare.

Adesso ti mostro il file html sempre del componente toolbar.

<ng-container *ngIf="device.isDesktop()">
    <desktop-toolbar></desktop-toolbar>
</ng-container>

<ng-container *ngIf="device.isTablet() || device.isMobile()">
    <mobile-toolbar></mobile-toolbar>
</ng-container>

Ecco che qui troviamo le varie funzioni isDesktop ecc… tramite un ngIf vado a mostrare la toolbar per desktop o quella per mobile.

Questo componente non ha codice SCSS.

Adesso passiamo alla toolbar per desktop, vediamo il suo Typescript.

import { ToolbarComponent } from '../toolbar.component';

@Component({
  selector: 'desktop-toolbar',
  templateUrl: './desktop-toolbar.component.html',
  styleUrls: ['./desktop-toolbar.component.scss']
})
export class DesktopToolbarComponent extends ToolbarComponent implements OnInit {

  constructor(
    public device: DeviceDetectorService
  ) {
    super(device)
  }

  ngOnInit(): void {
  }

}

L’unica particolarità è che questo componente estende quello visto in precedenza motivo per cui, nella dichiarazione della classe è presente “extends …”. Per poter estendere una classe Typescript occorre specificare il super() con gli argomenti del costruttore della classe “padre”.

Andiamo a vedere il template html.

<mat-toolbar color="primary">
    <div>{{ title }}</div>
    <div class="spacer"></div>
    <div class="item mat-body-2" *ngFor="let item of menu"> {{ item }} </div>
</mat-toolbar>

<router-outlet></router-outlet>

Viene istanziata una semplice mat-toolbar di Angular material, viene inserito il titolo sinistra e le voci del menù a destra, di seguito anche il file SCSS di questo componente

.spacer  {
    flex: 1 auto;
}

.item {
    margin-left: 20px;
}

Il risultato finale è qualcosa di simile a questo:

toolbar per desktop

Adesso andiamo a scrivere il codice anche della toolbar per mobile, incollerò i file Typescript, html e SCSS senza fornire un commento ai file stessi in quanto il commento sarebbe lo stesso fatto ai file precedenti.

import { Component, OnInit } from '@angular/core';
import { DeviceDetectorService } from 'ngx-device-detector';
import { ToolbarComponent } from '../toolbar.component';

@Component({
  selector: 'mobile-toolbar',
  templateUrl: './mobile-toolbar.component.html',
  styleUrls: ['./mobile-toolbar.component.scss']
})
export class MobileToolbarComponent extends ToolbarComponent implements OnInit {

  constructor(
    public device: DeviceDetectorService
  ) {
    super(device);
  }

  ngOnInit(): void {
  }

}
<mat-toolbar color="warn">
    <div>{{ title }}</div>
    <div class="spacer"></div>
    <mat-icon (click)="drawer.toggle()">menu</mat-icon>
</mat-toolbar>

<mat-drawer-container class="example-container" [hasBackdrop]="true">
    <mat-drawer #drawer mode="over">
        <div class="item mat-body-2" *ngFor="let item of menu"> {{ item }} </div>
    </mat-drawer>
    <mat-drawer-content>
        <router-outlet></router-outlet>
    </mat-drawer-content>
  </mat-drawer-container>
mat-toolbar {
    height: 64px;
}

.spacer {
    flex: 1 auto;
}

mat-drawer {
    width: 75vw;
    padding: 20px;
    box-sizing: border-box;
}

Il risultato ottenuto su dispositivi mobili e tablet è il seguente:

risultato mobile
risultato mobile pannello aperto

5. Osservazioni

Il risultato ottenuto non è niente male, abbiamo scritto due componenti totalmente diversi dal lato estetico ma che condividono le stesse proprietà e gli stessi metodi (qualora ci fossero stati) in quanto entrambe le versioni della toolbar derivano da un componente padre generico.

Utilizzando questa strategia di creazione dei componenti riusciamo a creare un progetto che funzioni e sia bello da vedere su qualsiasi dispositivo scrivendo un’unica applicazione.

6. Conclusioni

Ti ho mostrato come gestisco personalmente il multi-template su Angular, se hai suggerimenti per migliorarne l’implementazione scrivimi pure un commento qui sotto o sentiti libero di modificare a piacimento il codice di questo piccolo progetto di dimostrazione in quanto l’ho pubblicato su gitHub.

https://github.com/fabiobiffi/dynamic-template-angular

Ciao e alla prosisma 🙂

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

*