# Taller: Desarrollo con Angular e Ionic ## Capítulo 2: Componentes y Estructura de una Aplicación Angular/Ionic ## 1. Estructura de una Aplicación Angular/Ionic Al crear un nuevo proyecto de Ionic con Angular mediante el comando `ionic start`, se genera una estructura de carpetas organizada: ``` mi-app/ ├── src/ # Código fuente principal │ ├── app/ # Lógica y componentes de la aplicación │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app-routing.module.ts │ ├── assets/ # Recursos estáticos (imágenes, fuentes, etc.) │ ├── environments/ # Configuraciones por entorno (dev, prod) │ ├── theme/ # Variables globales de estilo (colores, etc.) │ ├── global.scss # Estilos globales │ ├── index.html # Archivo HTML raíz │ ├── main.ts # Punto de entrada de la aplicación ├── angular.json # Configuración de Angular ├── capacitor.config.ts # Configuración de Capacitor ├── package.json # Dependencias y scripts ├── tsconfig.json # Configuración de TypeScript ``` ### Archivos Clave: - **app.module.ts**: Módulo principal que configura la aplicación - **app-routing.module.ts**: Define las rutas de navegación - **app.component.ts**: Componente raíz que contiene toda la aplicación - **index.html**: Archivo HTML base donde se monta la aplicación - **main.ts**: Punto de entrada que arranca la aplicación Angular ## 2. Fundamentos de los Componentes Angular ### Anatomía de un Componente Cada componente Angular consta de: 1. **Clase TypeScript**: Contiene la lógica y datos 2. **Plantilla HTML**: Define la estructura visual 3. **Estilos CSS**: Define la apariencia (opcional) 4. **Metadatos**: Configuración mediante decorador @Component ```typescript import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-producto', // Cómo se referencia en HTML templateUrl: './producto.component.html', // Plantilla HTML styleUrls: ['./producto.component.scss'] // Estilos }) export class ProductoComponent implements OnInit { nombre: string = 'Smartphone'; precio: number = 599.99; disponible: boolean = true; constructor() { } ngOnInit() { // Inicialización del componente } aplicarDescuento(porcentaje: number): void { this.precio = this.precio * (1 - porcentaje/100); } } ``` ### Ciclo de Vida de los Componentes Los componentes tienen un ciclo de vida gestionado por Angular: 1. **ngOnChanges**: Cuando cambian las propiedades de entrada (@Input) 2. **ngOnInit**: Después de la primera inicialización 3. **ngAfterViewInit**: Cuando la vista se ha inicializado 4. **ngOnDestroy**: Justo antes de que Angular destruya el componente ```typescript import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { DatosService } from './datos.service'; @Component({ selector: 'app-ejemplo', template: '
{{datos}}
' }) export class EjemploComponent implements OnInit, OnDestroy { datos: any[] = []; private suscripcion!: Subscription; constructor(private datosService: DatosService) { } ngOnInit() { // Perfecto para inicializar datos, suscripciones, etc. this.suscripcion = this.datosService.obtenerDatos() .subscribe(datos => { this.datos = datos; }); } ngOnDestroy() { // Limpieza antes de destruir el componente if (this.suscripcion) { this.suscripcion.unsubscribe(); } } } ``` ## 3. Vinculación de Datos (Data Binding) Angular ofrece cuatro formas de vinculación de datos: ### Interpolación {{ }} Muestra valores de propiedades en la plantilla: ```html

{{ titulo }}

Precio: {{ precio | currency }}

Estado: {{ disponible ? 'En stock' : 'Agotado' }}
``` ### Property Binding [ ] Vincula propiedades HTML con valores del componente: ```html
Contenido con clases dinámicas
``` ### Event Binding ( ) Responde a eventos del usuario: ```html
``` ### Two-Way Binding [( )] Combina property binding y event binding para actualizar datos en ambas direcciones: ```html ``` > Nota: Para usar ngModel, debes importar FormsModule en tu módulo Angular. ## 4. Directivas en Angular Las directivas son clases que extienden HTML con nueva funcionalidad. ### Directivas Estructurales Modifican el DOM añadiendo o quitando elementos: ```html
El producto está disponible
Panel de administrador
Panel de editor
Panel de usuario
``` ### Directivas de Atributo Modifican la apariencia o comportamiento de elementos existentes: ```html
Elemento con clases dinámicas
Texto con estilo dinámico
``` ## 5. Componentes UI de Ionic - Visión General Ionic proporciona una amplia gama de componentes UI pre-diseñados que siguen las guías de diseño de iOS y Android. A continuación, se muestra una visión general de las categorías principales: ### Categorías de Componentes 1. **Navegación** - `ion-header`, `ion-toolbar`, `ion-buttons` - `ion-tabs` - `ion-menu` - `ion-back-button` 2. **Contenido y Presentación** - `ion-card` y componentes relacionados - `ion-list`, `ion-item` - `ion-grid`, `ion-row`, `ion-col` - `ion-avatar`, `ion-thumbnail` 3. **Formularios e Inputs** - `ion-input`, `ion-textarea` - `ion-select`, `ion-radio`, `ion-checkbox` - `ion-toggle`, `ion-range` - `ion-datetime` 4. **Feedback al Usuario** - `ion-loading` - `ion-toast` - `ion-alert` - `ion-action-sheet` ### Ejemplo Básico de Uso ```html Mi Aplicación Bienvenido Contenido de ejemplo para mostrar un card básico. Elemento 1 Elemento 2 ``` ### Controladores de Componentes Para componentes interactivos como alertas, modales o toasts, se utilizan controladores: ```typescript import { Component } from '@angular/core'; import { AlertController } from '@ionic/angular'; @Component({ selector: 'app-ejemplo', templateUrl: './ejemplo.page.html', }) export class EjemploPage { constructor(private alertController: AlertController) {} async mostrarAlerta() { const alert = await this.alertController.create({ header: 'Información', message: 'Esta es una alerta de ejemplo', buttons: ['OK'] }); await alert.present(); } } ``` > **Nota:** Para más detalles sobre cada componente, consulta la [documentación oficial de Ionic](https://ionicframework.com/docs/components), donde encontrarás ejemplos completos de uso y todas las propiedades disponibles. ## 6. Servicios y Dependency Injection Los servicios en Angular son clases que encapsulan lógica de negocio y compartir estado entre componentes. La inyección de dependencias facilita su uso. ### Creación de un Servicio ```bash ionic generate service services/productos ``` ```typescript import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Producto { id: number; nombre: string; precio: number; imagen: string; descripcion: string; } @Injectable({ providedIn: 'root' // Disponible en toda la aplicación }) export class ProductosService { private apiUrl = 'https://mi-api.com/productos'; constructor(private http: HttpClient) { } obtenerProductos(): Observable { return this.http.get(this.apiUrl); } obtenerProductoPorId(id: number): Observable { return this.http.get(`${this.apiUrl}/${id}`); } buscarProductos(termino: string): Observable { return this.http.get(this.apiUrl).pipe( map(productos => productos.filter(p => p.nombre.toLowerCase().includes(termino.toLowerCase()) )) ); } agregarProducto(producto: Producto): Observable { return this.http.post(this.apiUrl, producto); } actualizarProducto(producto: Producto): Observable { return this.http.put(`${this.apiUrl}/${producto.id}`, producto); } eliminarProducto(id: number): Observable { return this.http.delete(`${this.apiUrl}/${id}`); } } ``` ### Uso del Servicio en un Componente ```typescript import { Component, OnInit } from '@angular/core'; import { ProductosService, Producto } from '../../services/productos.service'; import { LoadingController } from '@ionic/angular'; @Component({ selector: 'app-lista-productos', templateUrl: './lista-productos.page.html', styleUrls: ['./lista-productos.page.scss'], }) export class ListaProductosPage implements OnInit { productos: Producto[] = []; cargando = false; error = false; constructor( private productosService: ProductosService, private loadingController: LoadingController ) { } async ngOnInit() { await this.cargarProductos(); } async cargarProductos() { this.cargando = true; const loading = await this.loadingController.create({ message: 'Cargando productos...' }); await loading.present(); this.productosService.obtenerProductos().subscribe({ next: (data) => { this.productos = data; this.error = false; }, error: (error) => { console.error('Error al cargar productos', error); this.error = true; }, complete: () => { this.cargando = false; loading.dismiss(); } }); } async refrescarProductos(event: any) { this.productosService.obtenerProductos().subscribe({ next: (data) => { this.productos = data; this.error = false; event.target.complete(); }, error: (error) => { console.error('Error al refrescar productos', error); this.error = true; event.target.complete(); } }); } buscarProductos(event: any) { const termino = event.detail.value; if (termino && termino.trim() !== '') { this.productosService.buscarProductos(termino).subscribe(productos => { this.productos = productos; }); } else { this.cargarProductos(); } } } ``` ## 7. Navegación y Enrutamiento Angular Router permite definir rutas para navegar entre páginas: ```typescript // app-routing.module.ts import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { path: '', redirectTo: 'inicio', pathMatch: 'full' }, { path: 'inicio', loadChildren: () => import('./pages/inicio/inicio.module').then(m => m.InicioPageModule) }, { path: 'productos', loadChildren: () => import('./pages/productos/productos.module').then(m => m.ProductosPageModule) }, { path: 'producto/:id', loadChildren: () => import('./pages/detalle-producto/detalle-producto.module').then(m => m.DetalleProductoPageModule) }, { path: 'carrito', loadChildren: () => import('./pages/carrito/carrito.module').then(m => m.CarritoPageModule) }, { path: 'perfil', loadChildren: () => import('./pages/perfil/perfil.module').then(m => m.PerfilPageModule) }, { path: '**', loadChildren: () => import('./pages/not-found/not-found.module').then(m => m.NotFoundPageModule) } ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) ], exports: [RouterModule] }) export class AppRoutingModule { } ``` ### Navegación Declarativa ```html Ver productos {{ producto.nombre }} Ver electrónica ``` ### Navegación Programática ```typescript import { Component } from '@angular/core'; import { Router, NavigationExtras } from '@angular/router'; @Component({ selector: 'app-navegacion', templateUrl: './navegacion.page.html', }) export class NavegacionPage { constructor(private router: Router) { } irAProductos() { this.router.navigate(['/productos']); } verProducto(id: number) { this.router.navigate(['/producto', id]); } filtrarProductos() { const navigationExtras: NavigationExtras = { queryParams: { categoria: 'electronica', orden: 'precio' } }; this.router.navigate(['/productos'], navigationExtras); } } ``` ### Recibir Parámetros ```typescript import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { ProductosService, Producto } from '../../services/productos.service'; @Component({ selector: 'app-detalle-producto', templateUrl: './detalle-producto.page.html', }) export class DetalleProductoPage implements OnInit { producto: Producto | undefined; constructor( private route: ActivatedRoute, private productosService: ProductosService ) { } ngOnInit() { // Parámetros de ruta this.route.paramMap.subscribe(params => { const id = Number(params.get('id')); if (id) { this.cargarProducto(id); } }); // Parámetros de consulta this.route.queryParamMap.subscribe(params => { const vista = params.get('vista'); console.log('Vista:', vista); }); } cargarProducto(id: number) { this.productosService.obtenerProductoPorId(id).subscribe(producto => { this.producto = producto; }); } } ``` ## 8. Integración con Capacitor Para utilizar capacidades nativas con Capacitor, necesitas instalar y configurar plugins específicos. ### Ejemplo: Cámara ```bash npm install @capacitor/camera npx cap sync ``` ```typescript import { Component } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; @Component({ selector: 'app-camara', template: ` Cámara Tomar Foto
Foto tomada
` }) export class CamaraPage { photoUrl: string | undefined; constructor() { } async tomarFoto() { try { const photo = await Camera.getPhoto({4. Las directivas extienden HTML con funcionalidades dinámicas quality: 90, allowEditing: true, resultType: CameraResultType.Uri, source: CameraSource.Camera }); // La propiedad webPath es la URL que se puede usar para mostrar la foto this.photoUrl = photo.webPath; } catch (error) { console.error('Error al tomar la foto', error); } } } ``` ### Ejemplo: Geolocalización ```bash npm install @capacitor/geolocation npx cap sync ``` ```typescript import { Component } from '@angular/core'; import { Geolocation, Position } from '@capacitor/geolocation'; @Component({ selector: 'app-geolocalizacion', template: ` Ubicación Obtener Ubicación Tu ubicación actual

Latitud: {{ coordenadas.latitude }}

Longitud: {{ coordenadas.longitude }}

Precisión: {{ coordenadas.accuracy }} metros

` }) export class GeolocalizacionPage { coordenadas: { latitude: number; longitude: number; accuracy: number; } | undefined; constructor() { } async obtenerUbicacion() { try { // Solicitar permisos primero const permisos = await Geolocation.checkPermissions(); if (permisos.location !== 'granted') { const solicitado = await Geolocation.requestPermissions(); if (solicitado.location !== 'granted') { throw new Error('Permiso de ubicación denegado'); } } const position = await Geolocation.getCurrentPosition({ enableHighAccuracy: true }); this.coordenadas = { latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy }; } catch (error) { console.error('Error al obtener ubicación', error); } } } ``` ## Resumen En este capítulo hemos explorado a fondo los componentes y estructura de una aplicación Angular/Ionic: 1. La estructura de carpetas sigue una organización lógica que separa código, estilos y recursos 2. Los componentes Angular encapsulan HTML, CSS y lógica en unidades coherentes 3. El enlace de datos permite la comunicación bidireccional entre vistas y lógica 4. Las directivas extienden HTML con funcionalidades dinámicas 5. Ionic proporciona componentes UI que se adaptan a iOS y Android automáticamente 6. Los servicios permiten compartir lógica y estado entre componentes 7. El enrutamiento facilita la navegación entre diferentes vistas 8. Capacitor extiende las aplicaciones con acceso a funcionalidades nativas Con estos fundamentos, estamos listos para combinar todos estos conceptos en una aplicación completa en el siguiente capítulo. ## Recursos Adicionales - [Guía de Componentes Angular](https://angular.io/guide/component-overview) - [Ciclo de Vida en Angular](https://angular.io/guide/lifecycle-hooks) - [Directivas en Angular](https://angular.io/guide/attribute-directives) - [Componentes UI de Ionic](https://ionicframework.com/docs/components) - [Servicios e Inyección de Dependencias](https://angular.io/guide/dependency-injection) - [Router de Angular](https://angular.io/guide/router) - [Documentación de Capacitor](https://capacitorjs.com/docs/apis)