Saltar al contenido principal
Neural Nativev0.1.0-beta.1Documentación

Importación

component.ts
import { NeuVirtualListComponent } from '@neural-ui/native/virtual-list';

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.

Todos los proyectos

Recorre una colección larga con filas reutilizadas. Selecciona un proyecto para mostrar su resumen bajo la lista.

Ver y copiar el ejemplo completo
basic.component.ts
import { Component, signal, computed } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuVirtualListComponent } from '@neural-ui/native/virtual-list';
import { View, Text } from '@ng-native/components';
import { NeuVirtualListItemDirective } from '@neural-ui/native/virtual-list';
import type { NeuOption } from '@neural-ui/native';

@Component({
  selector: 'app-example',
  imports: [NeuAvatarComponent, NeuVirtualListComponent, NeuVirtualListItemDirective, View, Text],
  template: `
    <view class="product-section"><view class="product-section"><text class="product-title" accessibilityRole="header">Todos los proyectos</text><text class="product-caption">100 proyectos · Un espacio</text></view>
    <neu-virtual-list emptyLabel="No hay proyectos" [items]="projects" [height]="320" [estimatedItemHeight]="100" (selected)="selected.set($event)" (endReached)="reachedEnd.set(true)">
    <ng-template neuVirtualListItem let-project><view class="product-row"><neu-avatar [label]="project.label" size="sm" /><view class="product-copy"><text class="product-heading">{{ project.label }}</text><text class="product-caption">{{ project.description }}</text></view></view></ng-template>
    </neu-virtual-list>
    @if (selectedProject(); as project) { <view class="product-surface"><text class="product-heading">{{ project.label }}</text><text class="product-caption">{{ project.description }}</text></view> }
    <text class="product-caption" accessibilityLiveRegion="polite">{{ reachedEnd() ? 'Has llegado al último proyecto' : 'Selecciona un proyecto para ver su resumen' }}</text></view>
  `,
  styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }

:host { flex: 1; flex-shrink: 1; min-height: 0; }
.product-scroll { flex: 1; flex-shrink: 1; height: 0px; min-height: 0; }
.product-stack { gap: 24px; padding-bottom: 20px; }
.product-section { gap: 12px; }
.product-row { flex-direction: row; align-items: center; gap: 12px; }
.product-copy { flex: 1; min-width: 0; gap: 4px; }
.product-title { color: var(--neu-text, #172033); font-size: 24px; line-height: 30px; font-weight: 600; }
.product-heading { color: var(--neu-text, #172033); font-size: 17px; line-height: 23px; font-weight: 600; }
.product-caption { color: var(--neu-muted, #596477); font-size: 14px; line-height: 20px; }
.product-actions { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px; }
.product-surface { gap: 16px; padding: 16px; border-radius: 16px; background-color: var(--neu-surface, #ffffff); border-width: 1px; border-color: var(--neu-border, #a5afbf); }

  `,
})
export class ExampleComponent {
  readonly projects: readonly NeuOption[] = Array.from({ length: 100 }, (_, index) => ({
    id: 'project-' + index,
    label: ['Lanzamiento móvil', 'Renovación de la web', 'Investigación con clientes', 'Sistema de diseño', 'Bienvenida al equipo', 'Revisión de accesibilidad', 'Notas del lanzamiento', 'Portal de clientes', 'Espacio sin conexión', 'Analítica del producto'][index % 10] + ' · ' + ['Descubrimiento', 'Prototipo', 'Piloto', 'Lanzamiento', 'Crecimiento', 'Conclusiones', 'Acabado', 'Despliegue', 'Comentarios', 'Siguiente etapa'][Math.floor(index / 10)],
    description: ['Equipo de producto · En curso', 'Equipo de diseño · En revisión', 'Equipo de clientes · Planificación'][index % 3],
  }));
  readonly selected = signal('');
  readonly selectedProject = computed(() => this.projects.find(item => item.id === this.selected()));
  readonly reachedEnd = signal(false);
}

Filas recicladas propias

Desplaza filas personalizadas con cabecera y pie. Cada plantilla recibe el índice original del elemento aunque la lista recicle sus vistas.

Ver y copiar el ejemplo completo
custom-items.component.ts
import { Component, signal, computed } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuVirtualListComponent } from '@neural-ui/native/virtual-list';
import { View, Text, ScrollView } from '@ng-native/components';
import { NeuVirtualListItemDirective } from '@neural-ui/native/virtual-list';

@Component({
  selector: 'app-example',
  imports: [NeuAvatarComponent, NeuVirtualListComponent, NeuVirtualListItemDirective, View, Text, ScrollView],
  template: `
    <scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
    <neu-virtual-list emptyLabel="No hay proyectos" [items]="members" [height]="360" [estimatedItemHeight]="80" (selected)="selected.set($event)">
      <view listHeader class="list-heading"><text class="usage-heading">Directorio del espacio</text><text class="usage-caption">24 miembros del equipo</text></view>
      <ng-template neuVirtualListItem let-member let-index="index" let-disabled="disabled">
        <view class="directory-row"><neu-avatar [label]="member.label" size="md" /><view class="directory-copy"><text class="usage-heading">{{ member.label }}</text><text class="usage-caption">{{ disabled ? 'No disponible' : member.description }}</text></view><text class="usage-caption">{{ index + 1 }}</text></view>
      </ng-template>
      <view listFooter class="list-heading"><text class="usage-caption">Fin del directorio</text></view>
    </neu-virtual-list>
    <text class="usage-caption">{{ selectedMember() ? 'Persona seleccionada: ' + selectedMember()?.label : 'Selecciona un miembro' }}</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; }

.directory-row { flex-direction: row; gap: 12px; align-items: center; } .directory-copy { flex: 1; min-width: 0; gap: 3px; } .list-heading { gap: 4px; padding: 12px 0; }
  `,
})
export class ExampleComponent {
  readonly members = Array.from({ length: 24 }, (_, index) => ({ id: 'member-' + index, label: ['Alex', 'Sam', 'Jordan', 'Taylor', 'Robin', 'Casey'][index % 6] + ' ' + ['Morgan', 'Rivera', 'Chen', 'Patel'][Math.floor(index / 6)], description: ['Diseñadora de producto', 'Desarrollador móvil', 'Ingeniero de diseño', 'Responsable de producto'][index % 4], disabled: index === 3 }));
  readonly selected = signal('');
  readonly selectedMember = computed(() => this.members.find(member => member.id === this.selected()));
}
Playground interactivo

Ajusta la configuración y prueba el resultado.

Capturas del dispositivo en inglés.

Virtual List · android · native capture
AndroidCaptura nativa
Virtual List · ios · native capture
iOSCaptura nativa

Personalización

Importa estas directivas junto con el componente y añádelas a Component.imports. Los ejemplos anteriores incluyen la configuración completa.

personalization-imports.ts
import { NeuVirtualListItemDirective } from '@neural-ui/native/virtual-list';
Plantilla o regiónContextoUso
ng-template[neuVirtualListItem]$implicit: NeuOption; index: number; disabled: booleanContenido visual de cada fila. La lista conserva el reciclado, la medición y la selección. index es la posición del elemento, no la del slot reciclado; guarda el estado por item.id.
[listHeader]—View nativa antes de las filas desplazables; no necesita una directiva Neural adicional.
[listFooter]—View nativa después de las filas desplazables; no necesita una directiva Neural adicional.

Contextos de las plantillas

template-contexts.ts
import type { NeuVirtualListItemContext } from '@neural-ui/native/virtual-list';

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.

Referencia API

Inputs

NombreTipoPredeterminadoEnlaceDescripción
itemsreadonly NeuOption[][]InputDatos ordenados del componente. Utiliza el contrato siguiente e identificadores estables y únicos.
disabledbooleanfalseInputDesactiva la interacción del usuario. La aplicación puede seguir proporcionando valores.
estimatedItemHeightnumber84InputAltura estimada de fila utilizada para calcular la ventana virtualizada.
heightnumber400InputAltura en unidades de layout nativo del viewport o contenido, según el componente.
emptyLabelstring'No items'InputTexto o nombre accesible utilizado para estado vacío. No ejecuta la acción por sí solo.

Models

Este componente no declara models bidireccionales.

Outputs

NombrePayloadCuándo se emite
selectedstringEmite el ID del elemento seleccionado.
endReachedvoidComunica que se alcanza el final de la lista visible. Evita cargas simultáneas en la aplicación.

Tipos e interfaces de datos

Utiliza los imports en tu aplicación. Las definiciones siguientes se muestran como referencia.

NeuOption

NeuOption.ts
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;
}
CampoTipoRequisitoSignificado
idstringObligatorioIdentificador estable y único para seguimiento y selección.
labelstringObligatorioNombre legible del elemento.
descriptionstringOpcionalTexto de apoyo opcional; su presentación depende del control.
disabledbooleanOpcionalImpide activar o seleccionar este elemento.