Datos y multimedia
Data View
Busca en una colección, selecciona un resultado y restablece la búsqueda.
Importación
import { NeuDataViewComponent } from '@neural-ui/native/data-view';Ejemplos
Importa ExampleComponent en su padre y coloca <app-example /> dentro del neu-theme raíz de tu aplicación. El preview aporta el marco y las zonas seguras; utiliza la estructura raíz de Instalación en tu app.
Básico
Utiliza una plantilla Angular para cada fila. Este listado de personas combina Avatar y texto; Data View conserva la búsqueda, el orden y la selección.
Ver y copiar el ejemplo completo
import { Component, signal, computed } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuDataViewComponent } from '@neural-ui/native/data-view';
import { View, Text } from '@ng-native/components';
import type { NeuOption } from '@neural-ui/native';
@Component({
selector: 'app-example',
imports: [NeuAvatarComponent, NeuDataViewComponent, View, Text],
template: `
<view class="people-header">
<view class="people-heading">
<text class="people-title">Miembros del equipo</text>
<text class="people-subtitle">Espacio del producto</text>
</view>
<view class="people-count"><text class="people-count-label">{{ people().length }}</text></view>
</view>
<ng-template #person let-item>
<view class="person-row" [class.person-row-selected]="selected() === item.id">
<neu-avatar [label]="item.label" size="md" />
<view class="person-copy">
<text class="person-name">{{ item.label }}</text>
<text class="person-description">{{ item.description }}</text>
</view>
<view class="person-end" [accessibilityElementsHidden]="true" importantForAccessibility="no-hide-descendants">
@if (selected() === item.id) {
<view class="person-selected-mark"><text class="person-check">✓</text></view>
} @else {
<view class="person-chevron" />
}
</view>
</view>
</ng-template>
<neu-data-view emptyLabel="No hay resultados" refreshLabel="Actualizar" sortLabel="Invertir orden" [items]="people()" [itemTemplate]="person" searchLabel="Buscar personas" [(query)]="query" [(descending)]="descending" (selected)="selected.set($event)" (refresh)="refreshPeople()" />
<view class="people-footer">
<text class="people-status" accessibilityLiveRegion="polite">{{ selected() ? selectedName() + ' · Selección actual' : 'Selecciona a una persona' }}</text>
@if (refreshed()) { <text class="people-updated" accessibilityLiveRegion="polite">Actualizado</text> }
</view>
`,
styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }
.people-header { flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.people-heading { flex: 1; min-width: 0; gap: 4px; }
.people-title { font-size: 22px; font-weight: 700; }
.people-subtitle { font-size: 13px; color: var(--neu-muted, #596477); }
.people-count { min-width: 28px; padding: 4px 9px; align-items: center; border-radius: 999px; background-color: var(--neu-tonal, #eff6ff); }
.people-count-label { font-size: 12px; font-weight: 600; color: var(--neu-accent, #006ee6); }
.person-row { flex-direction: row; align-items: center; gap: 12px; padding: 12px; border-width: 1px; border-color: var(--neu-border, #a5afbf); border-radius: 12px; background-color: var(--neu-surface, #ffffff); box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04); transition: background-color 180ms ease-out, border-color 180ms ease-out; }
@media (prefers-reduced-motion: reduce) { .person-row { transition: none; } }
.person-row-selected { border-color: var(--neu-accent, #006ee6); background-color: var(--neu-tonal, #eff6ff); }
.person-copy { flex: 1; min-width: 0; gap: 4px; }
.person-name { font-size: 15px; font-weight: 600; }
.person-description { font-size: 13px; color: var(--neu-muted, #596477); }
.person-end { width: 20px; height: 24px; align-items: center; justify-content: center; }
.person-chevron { width: 6px; height: 6px; border-top-width: 1.5px; border-right-width: 1.5px; border-color: var(--neu-muted, #596477); transform: rotate(45deg); opacity: 0.6; }
.person-selected-mark { width: 20px; height: 20px; align-items: center; justify-content: center; border-radius: 10px; background-color: var(--neu-accent, #006ee6); }
.person-check { font-size: 12px; font-weight: 600; color: var(--neu-on-accent, #ffffff); }
.people-footer { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; min-height: 20px; margin-top: 12px; }
.people-status { font-size: 12px; color: var(--neu-muted, #596477); }
.people-updated { font-size: 12px; color: var(--neu-accent, #006ee6); }
`,
})
export class ExampleComponent {
readonly initialPeople: readonly NeuOption[] = [
{
"id": "ada",
"label": "Ada Lovelace",
"description": "Diseñadora de producto"
},
{
"id": "grace",
"label": "Grace Hopper",
"description": "Ingeniería móvil"
},
{
"id": "linus",
"label": "Linus Torvalds",
"description": "Ingeniero de plataforma"
}
];
readonly people = signal(this.initialPeople);
readonly query = signal('');
readonly descending = signal(false);
readonly selected = signal('');
readonly refreshed = signal(false);
readonly selectedName = computed(() => this.people().find(person => person.id === this.selected())?.label ?? 'Todavía ninguno');
refreshPeople(): void {
this.people.set([...this.initialPeople]);
this.refreshed.set(true);
}
}Plantilla de elemento declarativa
Utiliza la plantilla declarativa tipada como alternativa a itemTemplate. Su índice sigue la lista filtrada y ordenada.
Ver y copiar el ejemplo completo
import { Component, signal } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuDataViewComponent } from '@neural-ui/native/data-view';
import { View, Text, ScrollView } from '@ng-native/components';
import { NeuDataViewItemDirective } from '@neural-ui/native/data-view';
@Component({
selector: 'app-example',
imports: [NeuAvatarComponent, NeuDataViewComponent, NeuDataViewItemDirective, View, Text, ScrollView],
template: `
<scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
<neu-data-view emptyLabel="No hay resultados" refreshLabel="Actualizar" sortLabel="Invertir orden" [items]="people" searchLabel="Buscar personas" (selected)="selected.set($event)">
<ng-template neuDataViewItem let-person let-index="index">
<view class="member-row"><neu-avatar [label]="person.label" size="md" /><view class="member-copy"><text class="usage-heading">{{ person.label }}</text><text class="usage-caption">{{ person.description }}</text></view><text class="usage-caption">{{ index + 1 }}</text></view>
</ng-template>
</neu-data-view>
<text class="usage-caption" accessibilityLiveRegion="polite">{{ selected() ? 'Persona seleccionada: ' + selected() : 'Selecciona a una persona' }}</text>
</view></scroll-view>
`,
styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }
.usage-scroll { flex: 1; }
.usage-stack { gap: 20px; padding-bottom: 20px; }
.usage-section { gap: 12px; }
.usage-heading { color: var(--neu-text, #172033); font-size: 16px; font-weight: 600; }
.usage-caption { color: var(--neu-muted, #64748b); font-size: 14px; }
.usage-row { flex-direction: row; flex-wrap: wrap; gap: 12px; align-items: center; }
.usage-actions { flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center; }
.member-row { flex-direction: row; align-items: center; gap: 12px; padding: 12px; border-radius: 12px; border-width: 1px; border-color: var(--neu-border, #a5afbf); background-color: var(--neu-surface, #ffffff); } .member-copy { flex: 1; min-width: 0; gap: 4px; }
`,
})
export class ExampleComponent {
readonly people = [{ id: 'alex', label: 'Alex Morgan', description: 'Diseñadora de producto' }, { id: 'sam', label: 'Sam Rivera', description: 'Desarrollador móvil' }];
readonly selected = signal('');
}Ajusta la configuración y prueba el resultado.
Capturas del dispositivo en inglés.


Personalización
Importa estas directivas junto con el componente y añádelas a Component.imports. Los ejemplos anteriores incluyen la configuración completa.
import { NeuDataViewItemDirective } from '@neural-ui/native/data-view';| Plantilla o región | Contexto | Uso |
|---|---|---|
ng-template[neuDataViewItem] | $implicit: NeuOption; index: number | Sustituye el contenido visual de cada fila. La fila conserva el filtrado, la ordenación, el estado desactivado y el evento selected. index sigue la lista filtrada y ordenada. |
Contextos de las plantillas
import type { NeuDataViewItemContext } from '@neural-ui/native/data-view';let-item recibe $implicit; los campos con nombre utilizan let-index="index". Usa los tipos exportados al declarar un TemplateRef reutilizable. Mantén el contenido interno como presentación: el padre conserva la acción accesible y el comportamiento de selección.
Plantillas reutilizables
También puedes declarar <ng-template #row let-item> y enlazar [itemTemplate]="row". Un itemTemplate explícito tiene prioridad sobre la directiva proyectada; si no aportas ninguno, se muestra el contenido predeterminado.
Referencia API
Inputs
| Nombre | Tipo | Predeterminado | Enlace | Descripción |
|---|---|---|---|---|
| items | readonly NeuOption[] | [] | Input | Datos ordenados del componente. Utiliza el contrato siguiente e identificadores estables y únicos. |
| itemTemplate | TemplateRef<NeuDataViewItemContext> | undefined | undefined | Input | Plantilla Angular para cada fila visible. let-item recibe el elemento original; let-index="index" recibe su posición tras filtrar y ordenar. La plantilla controla el contenido visual; Data View conserva la selección y el estado disabled. Sin plantilla se utiliza la tarjeta de texto predeterminada. |
| loading | boolean | false | Input | Sustituye la lista de resultados por un skeleton y desactiva la actualización mientras la aplicación carga los datos. La búsqueda y la ordenación siguen disponibles. |
| error | string | '' | Input | Sustituye los resultados por una alerta con este mensaje cuando loading es false. Borra el mensaje para volver a mostrar los resultados. |
| searchLabel | string | 'Search items' | Input | Texto o nombre accesible utilizado para buscar. No ejecuta la acción por sí solo. |
| sortLabel | string | 'Reverse order' | Input | Nombre accesible de la acción con icono que invierte el orden de la lista. La acción se alinea a la derecha de la barra. |
| refreshLabel | string | 'Refresh' | Input | Nombre accesible del icono de actualización. Emite refresh para que tu aplicación recargue los datos y se desactiva mientras loading está activo. |
| emptyLabel | string | 'No items' | Input | Texto o nombre accesible utilizado para estado vacío. No ejecuta la acción por sí solo. |
Models
| Nombre | Tipo | Predeterminado | Enlace | Descripción |
|---|---|---|---|---|
| query | string | '' | Model · bidireccional | Texto de búsqueda actual utilizado por el control. Enlaza una signal escribible mediante [(query)]. |
| descending | boolean | false | Model · bidireccional | Indica si la dirección de orden solicitada es descendente. Enlaza una signal escribible mediante [(descending)]. |
Outputs
| Nombre | Payload | Cuándo se emite |
|---|---|---|
| queryChange | string | Emite el valor actualizado de query. [(query)] enlaza este output junto con su input model. |
| descendingChange | boolean | Emite el valor actualizado de descending. [(descending)] enlaza este output junto con su input model. |
| selected | string | Emite el ID del elemento seleccionado. |
| refresh | void | Solicita actualizar; carga los datos nuevos en tu aplicación. |
Estado de los models
Enlaza la signal con [(nombreModel)] sin llamarla. Utiliza nombreModel() para leer su valor.
import { signal } from '@angular/core';
// Fields inside your component class
readonly query = signal<string>('');
readonly descending = signal<boolean>(false);Tipos e interfaces de datos
Utiliza los imports en tu aplicación. Las definiciones siguientes se muestran como referencia.
NeuOption
import type { NeuOption } from '@neural-ui/native';
// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuOption {
readonly id: string;
readonly label: string;
readonly description?: string;
readonly disabled?: boolean;
}| Campo | Tipo | Requisito | Significado |
|---|---|---|---|
| id | string | Obligatorio | Identificador estable y único para seguimiento y selección. |
| label | string | Obligatorio | Nombre legible del elemento. |
| description | string | Opcional | Texto de apoyo opcional; su presentación depende del control. |
| disabled | boolean | Opcional | Impide activar o seleccionar este elemento. |
NeuDataViewItemContext
import type { NeuDataViewItemContext } from '@neural-ui/native/data-view';
import type { NeuOption } from '@neural-ui/native';
// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuDataViewItemContext {
readonly $implicit: NeuOption;
/** Position in the currently filtered and sorted collection. */
readonly index: number;
}| Campo | Tipo | Requisito | Significado |
|---|---|---|---|
| $implicit | NeuOption | Obligatorio | Elemento original suministrado a la plantilla de fila; se recibe mediante let-item. |
| index | number | Obligatorio | Posición desde cero en la colección visible; se recibe mediante let-index="index". La sección de personalización explica cómo aplica este componente el filtrado, la ordenación o el reciclado de filas. |