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 -->
<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 -->
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 -->
[ApiController]
[Route("api/samples")]
public class SamplesController : ControllerBase
{
[HttpGet("inventory")]
public IActionResult GetInventory()
{Trade-off
| Pro | Contro |
|---|---|
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 libero | CRUD non funziona out-of-the-box: edit/insert vanno wirati a mano sul tuo backend |
| Buono per integrazioni 3rd-party | Paging/sort/filter server-side richiede wiring custom (vedi sotto) |
| Nessun lavoro di scaffolding | Type 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:
| Valore | Significato | Quando 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). |
false | La 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)
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.
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):
[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: leggids.currentPage,ds.pageSize,ds.sortInfo[0],ds.filterInfo.filters[0]. - Il backend deve ritornare
{ rows, total }dovetotale' 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
operatoredella 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.
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.GetInventorycon 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
| Esempio | Strategia | Backend | Quando usarla |
|---|---|---|---|
| 3a | Client-side | Endpoint REST classico che ritorna un array | Dataset piccolo-medio (< qualche migliaio di righe), semplicita' massima, API 3rd-party senza controllo server-side |
| 3b | Server-side REST custom | Controller REST tuo con query params di paging/sort/filter | Dataset grande, vuoi controllo totale sulla query; l'end-dev ha gia' un endpoint esistente con offset/limit/ecc. |
| 3c | Server-side OData | Endpoint 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
- Pattern 1 — Full autogeneration: se l'UX standard basta e i dati esistono nel modello scaffoldato.
- Pattern 2 — Framework data + Custom component: inverso (UI custom, dati framework).
- Pattern 4 — Full custom: se non ti serve nemmeno la list-grid.
- Pattern 5 — Framework component + Framework data (manual mount): variante "framework" di questo pattern: stessa compozione manuale dei widget, ma data layer metadata-driven invece che backend custom.