Overview

Pattern: Framework component + Custom data

I componenti UI ad alto livello del framework (<wuic-list-grid>, <wuic-chart-list>, ...) accettano un datasource costruito a mano (hardcodedDatasource) che non passa dal data layer del framework. I dati arrivano dal tuo backend custom (Controller .NET tuo, REST esterna, file statici, websocket, ...).

Quando usarlo

  • Vuoi l'UX completa di una list-grid/chart-list (filtri, sort, paging client, export, edit dialog) ma i dati sono prodotti da:

- Un endpoint REST esterno (3rd-party API, microservizio).

- Un Controller .NET tuo non integrato col data layer del framework.

- File statici, calcoli aggregati, dati live (websocket, polling).

  • Hai un dominio legacy che non vuoi modellare nel framework.
  • Stai prototipando senza ancora aver definito la struttura dati.

Architettura

  • Sviluppatore: scrive un piccolo componente Angular che recupera i dati dal proprio backend e li impacchetta in un datasource locale.
  • Framework: la list-grid si comporta esattamente come se i dati venissero dal data layer standard (filtri, sort, paging, export funzionano).
  • Backend: liberta' totale. Endpoint REST classici, niente convenzioni di metadata.

Cosa fai tu (frontend)

Crei un componente Angular standalone che:

1. Chiama il tuo endpoint custom con HttpClient.

2. Definisce le colonne (nome, label, tipo) per il datasource locale.

3. Pubblica righe + colonne sul datasource e lo passa alla list-grid.

<!-- source: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/3a-external-rest-grid.component.html -->

Snippet 1HTML
<wuic-data-source #ds></wuic-data-source>
<wuic-list-grid [hardcodedDatasource]="ds" [hideToolbar]="false"></wuic-list-grid>

<!-- source: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/3a-external-rest-grid.component.ts -->

Snippet 2ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { DataSourceComponent, ListGridComponent, MetaInfo, MetadatiColonna, MetadatiTabella } from 'wuic-framework-lib';

@Component({
  selector: 'app-external-rest-grid',

Cosa fai tu (backend, opzionale)

Se i dati vengono dal tuo backend interno, basta un Controller REST classico. Niente convenzioni del framework, niente metadata da scrivere.

<!-- source: WuicTest/Controllers/SamplesController.cs -->

Snippet 3C#
[ApiController]
[Route("api/samples")]
public class SamplesController : ControllerBase
{
    [HttpGet("inventory")]
    public IActionResult GetInventory()
    {

Trade-off

ProContro
UX wuic-list-grid con filtri/sort/paging/export gratis (client-side)Mantieni tu la coerenza tra le righe e la definizione delle colonne
Backend completamente liberoCRUD non funziona out-of-the-box: edit/insert vanno wirati a mano sul tuo backend
Buono per integrazioni 3rd-partyPaging/sort/filter server-side richiede wiring custom (vedi sotto)
Nessun lavoro di scaffoldingType safety solo via cast

Filtri / sort / paging: client-side vs server-side

La frase "filtri/sort/paging/export gratis" della tabella Trade-off vale solo in modalita' client-side, ed e' subordinata a un flag metadata che va impostato esplicitamente nel datasource hardcoded.

Il flag chiave: md_server_side_operations

md_server_side_operations (proprieta' di MetadatiTabella) controlla dove vengono eseguite paging/sort/filter:

ValoreSignificatoQuando usarlo
true (default)La list-grid invia gli eventi paging/sort/filter al backend tramite l'endpoint CRUD standard del framework. Il backend ritorna solo la pagina richiesta e applica sort/filter SQL-side.Pattern 1 e 2 (con route metadata reale e backend WUIC dietro).
falseLa list-grid esegue paging/sort/filter in-memory sull'array gia' caricato. Niente roundtrip server.Sempre nei datasource hardcoded del Pattern 3 (e ogni volta che pubblichi tutte le righe in un colpo solo via fetchInfo$.next).

Trappola tipica del Pattern 3: se dimentichi di forzare md_server_side_operations: false, la list-grid mostra le 50/100 righe ricevute, ma cliccare la pagina 2, ordinare una colonna o digitare nel filtro non fa niente — la grid invia l'evento al "backend del framework" che non esiste, e l'UX appare bloccata pur senza errori in console.

> Nota framework: DataSourceComponent.fetchData() rileva automaticamente il caso "hardcoded datasource" (nessun [hardcodedRoute] impostato) e salta la chiamata al backend ad ogni cambio paging/sort/filter, ripubblicando il payload gia' presente in memoria su fetchInfo$. Significa che, una volta popolato fetchInfo$.next(...) la prima volta nel tuo ngAfterViewInit, paging/sort/filter funzionano client-side senza nessun roundtrip server, anche se il backend WUIC non ha registrato la route. Vedi data-source.component.ts (short-circuit dentro fetchData()).

Modalita' client-side (default consigliato per Pattern 3)

Snippet 4ts
const meta = new MetaInfo();
// new MetadatiTabella() per ereditare i default (md_sortable, ecc.).
const tableMeta = new MetadatiTabella();
tableMeta.md_server_side_operations = false;  // <-- chiave: forza in-memory
tableMeta.md_pageable = true;                  // abilita paginazione UI
tableMeta.md_pagesize = 10;                    // righe per pagina
meta.tableMetadata = tableMeta;
  • Carichi tutte le righe con un singolo fetchInfo$.next.
  • La list-grid applica filtri/sort/paging/export sull'array gia' presente.
  • Zero codice aggiuntivo.
  • Indicato per dataset piccoli/medi (ordine di qualche migliaio di righe).

Modalita' server-side (wiring manuale)

Per dataset grandi (decine/centinaia di migliaia di righe) non vuoi caricare tutto in memoria. Lasci md_server_side_operations: true (default), ti sottoscrivi agli @Output di `<wuic-list-grid>` (onPaging, onSorting, onFiltering) e re-chiami il tuo endpoint REST a ogni cambio di stato. La list-grid aggiorna ds.currentPage / pageSize / sortInfo / filterInfo prima di emettere l'evento, quindi nel handler basta leggere lo stato corrente.

Snippet 5ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { DataSourceComponent, ListGridComponent, MetaInfo, MetadatiColonna, MetadatiTabella, WtoolboxService } from 'wuic-framework-lib';

interface InventoryResponse { rows: any[]; total: number; }

Endpoint server complementare (esempio C#, vedi SamplesController.GetInventory):

Snippet 6C#
[HttpGet("inventory")]
public IActionResult GetInventory(
    int offset = 0,
    int limit = 10,
    string? sortField = null,
    string? sortDir = "asc",
    string? filterField = null,

Punti chiave:

  • Gli @Output (onPaging) / (onSorting) / (onFiltering) di <wuic-list-grid> espongono gli eventi UI dopo che il list-grid handler ha gia' aggiornato lo stato del datasource. Niente da reimplementare: leggi ds.currentPage, ds.pageSize, ds.sortInfo[0], ds.filterInfo.filters[0].
  • Il backend deve ritornare { rows, total } dove total e' il count POST-filter / PRE-page. Senza questo il pager UI non sa quante pagine esistono e non funziona correttamente.
  • L'operatore di filtro arriva nel campo operatore della filter entry ('eq', 'contains', 'startswith', ecc., vedi tabella matchMode in List Grid). Mappalo coerentemente lato server.

Variante: consumare l'endpoint OData del framework

Se l'entita' che vuoi visualizzare e' gia' esposta dal framework come entity set OData (/odata/<EntitySet>), non serve scrivere NESSUN controller: basta tradurre lo stato UI della list-grid in query string OData v4 standard.

> Alternativa 100% framework-driven (Pattern 1 con backend OData): se accetti di registrare una route metadata standard per l'entita', puoi configurare md_props_bag.endpoint = {"type":"odata","uri":"/odata/Cities"} e il datasource fa tutto da solo (filter/sort/paging/export) tramite il provider OData interno. Niente codice Angular custom. Vedi OData per il setup completo. Pattern 3 (questa pagina) si applica invece quando vuoi controllo esplicito lato frontend o non hai metadata registrati per l'entita'.

Il framework espone DataProviderOdataService.filterInfoToOdata(filterInfo, entitySetName) che fa tutto il mapping operator WUIC -> $filter OData (contains/startswith/endswith/eq/ne/gt/ge/lt/le) con quoting automatico per string/numeric, supporto nested filter groups (AND/OR ricorsivi) e isnull/isnotnull. Il return e' una URL relativa tipo /odata/Cities?$filter=<espressione encoded>. Ti basta prefissare la base URL e aggiungere $top / $skip / $orderby.

Snippet 7ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, forkJoin } from 'rxjs';
import {
  DataProviderOdataService,
  DataSourceComponent, ListGridComponent,
  FilterInfo, MetaInfo, MetadatiColonna, MetadatiTabella,

> Nota su total/count: se l'endpoint OData che usi e' configurato per ritornare il wrapper OData standard { value: [...], "@odata.count": N } (via $count=true), puoi leggere il total direttamente dalla response senza la seconda query. L'endpoint del framework WUIC attualmente ritorna il plain array e richiede la query parallela.

Esempi vivi nel WuicTest

I tre esempi coprono le tre strategie principali del Pattern 3:

  • External REST grid (client-side) → carica TUTTI i 50 post da un endpoint esterno (jsonplaceholder.typicode.com/posts) in un colpo solo, paging/sort/filter applicati in-memory dalla list-grid (md_server_side_operations: false). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/. Apri demo.
  • Custom .NET grid (server-side, REST custom) → chiama il Controller SamplesController.GetInventory con offset/limit/sort/filter come query params ad hoc, ricarica solo la pagina corrente ad ogni cambio (md_server_side_operations: true + wiring esplicito su (onPaging)/(onSorting)/(onFiltering)). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3b-custom-dotnet-grid/ + Controllers/SamplesController.cs. Apri demo.
  • OData Cities grid (server-side, standard OData v4) → consuma l'endpoint OData generico del framework (GET /odata/Cities) con query string standard $top / $skip / $filter / $orderby, nessun controller custom da scrivere. Traduce gli eventi UI della list-grid in query OData (contains(name,'v'), field eq value, ecc.). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3c-odata-cities-grid/. Apri demo.

Quando scegliere quale variante

EsempioStrategiaBackendQuando usarla
3aClient-sideEndpoint REST classico che ritorna un arrayDataset piccolo-medio (< qualche migliaio di righe), semplicita' massima, API 3rd-party senza controllo server-side
3bServer-side REST customController REST tuo con query params di paging/sort/filterDataset grande, vuoi controllo totale sulla query; l'end-dev ha gia' un endpoint esistente con offset/limit/ecc.
3cServer-side ODataEndpoint OData del framework (/odata/<EntitySet>)Dataset grande esposto automaticamente dal framework come OData set; zero codice backend; sintassi standard compatibile con altri client

Vedi anche