769 lines
20 KiB
Markdown
769 lines
20 KiB
Markdown
# 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: '<div>{{datos}}</div>'
|
|
})
|
|
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
|
|
<h1>{{ titulo }}</h1>
|
|
<p>Precio: {{ precio | currency }}</p>
|
|
<div>Estado: {{ disponible ? 'En stock' : 'Agotado' }}</div>
|
|
|
|
```
|
|
|
|
### Property Binding [ ]
|
|
|
|
Vincula propiedades HTML con valores del componente:
|
|
|
|
```html
|
|
<img [src]="imagenUrl">
|
|
<button [disabled]="!formularioValido">Enviar</button>
|
|
<div [ngClass]="{'destacado': esDestacado, 'oculto': !visible}">
|
|
Contenido con clases dinámicas
|
|
</div>
|
|
|
|
```
|
|
|
|
### Event Binding ( )
|
|
|
|
Responde a eventos del usuario:
|
|
|
|
```html
|
|
<button (click)="agregarAlCarrito()">Agregar al carrito</button>
|
|
<input (input)="actualizarBusqueda($event)">
|
|
<form (submit)="enviarFormulario()">
|
|
<!-- Campos del formulario -->
|
|
</form>
|
|
|
|
```
|
|
|
|
### Two-Way Binding [( )]
|
|
|
|
Combina property binding y event binding para actualizar datos en ambas direcciones:
|
|
|
|
```html
|
|
<input [(ngModel)]="nombreUsuario">
|
|
|
|
<!-- Equivalente a: -->
|
|
<input [value]="nombreUsuario" (input)="nombreUsuario = $event.target.value">
|
|
|
|
```
|
|
|
|
> 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
|
|
<!-- *ngIf: Condicional -->
|
|
<div *ngIf="producto.disponible">
|
|
El producto está disponible
|
|
</div>
|
|
|
|
<!-- *ngFor: Repetición -->
|
|
<ul>
|
|
<li *ngFor="let producto of productos; let i = index">
|
|
{{i+1}}. {{producto.nombre}} - {{producto.precio | currency}}
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- *ngSwitch: Condicional múltiple -->
|
|
<div [ngSwitch]="rol">
|
|
<div *ngSwitchCase="'admin'">Panel de administrador</div>
|
|
<div *ngSwitchCase="'editor'">Panel de editor</div>
|
|
<div *ngSwitchDefault>Panel de usuario</div>
|
|
</div>
|
|
|
|
```
|
|
|
|
### Directivas de Atributo
|
|
|
|
Modifican la apariencia o comportamiento de elementos existentes:
|
|
|
|
```html
|
|
<!-- ngClass: Clases dinámicas -->
|
|
<div [ngClass]="{'activo': estaActivo, 'destacado': esDestacado}">
|
|
Elemento con clases dinámicas
|
|
</div>
|
|
|
|
<!-- ngStyle: Estilos dinámicos -->
|
|
<div [ngStyle]="{'color': colorTexto, 'font-size.px': tamanoFuente}">
|
|
Texto con estilo dinámico
|
|
</div>
|
|
|
|
<!-- ngModel: Two-way binding (requiere FormsModule) -->
|
|
<input [(ngModel)]="nombre" placeholder="Escribe tu nombre">
|
|
|
|
```
|
|
|
|
## 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
|
|
<!-- Ejemplo de una página con algunos componentes básicos -->
|
|
<ion-header>
|
|
<ion-toolbar color="primary">
|
|
<ion-title>Mi Aplicación</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-content>
|
|
<ion-card>
|
|
<ion-card-header>
|
|
<ion-card-title>Bienvenido</ion-card-title>
|
|
</ion-card-header>
|
|
<ion-card-content>
|
|
Contenido de ejemplo para mostrar un card básico.
|
|
</ion-card-content>
|
|
</ion-card>
|
|
|
|
<ion-list>
|
|
<ion-item>
|
|
<ion-label>Elemento 1</ion-label>
|
|
</ion-item>
|
|
<ion-item>
|
|
<ion-label>Elemento 2</ion-label>
|
|
</ion-item>
|
|
</ion-list>
|
|
</ion-content>
|
|
|
|
```
|
|
|
|
### 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<Producto[]> {
|
|
return this.http.get<Producto[]>(this.apiUrl);
|
|
}
|
|
|
|
obtenerProductoPorId(id: number): Observable<Producto> {
|
|
return this.http.get<Producto>(`${this.apiUrl}/${id}`);
|
|
}
|
|
|
|
buscarProductos(termino: string): Observable<Producto[]> {
|
|
return this.http.get<Producto[]>(this.apiUrl).pipe(
|
|
map(productos => productos.filter(p =>
|
|
p.nombre.toLowerCase().includes(termino.toLowerCase())
|
|
))
|
|
);
|
|
}
|
|
|
|
agregarProducto(producto: Producto): Observable<Producto> {
|
|
return this.http.post<Producto>(this.apiUrl, producto);
|
|
}
|
|
|
|
actualizarProducto(producto: Producto): Observable<Producto> {
|
|
return this.http.put<Producto>(`${this.apiUrl}/${producto.id}`, producto);
|
|
}
|
|
|
|
eliminarProducto(id: number): Observable<any> {
|
|
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
|
|
<!-- Enlace simple -->
|
|
<ion-button routerLink="/productos">Ver productos</ion-button>
|
|
|
|
<!-- Con parámetros -->
|
|
<ion-item [routerLink]="['/producto', producto.id]">
|
|
{{ producto.nombre }}
|
|
</ion-item>
|
|
|
|
<!-- Con parámetros de consulta -->
|
|
<a [routerLink]="['/productos']" [queryParams]="{categoria: 'electronica'}">
|
|
Ver electrónica
|
|
</a>
|
|
|
|
```
|
|
|
|
### 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: `
|
|
<ion-header>
|
|
<ion-toolbar>
|
|
<ion-title>Cámara</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-content class="ion-padding">
|
|
<ion-button expand="block" (click)="tomarFoto()">
|
|
<ion-icon name="camera" slot="start"></ion-icon>
|
|
Tomar Foto
|
|
</ion-button>
|
|
|
|
<div *ngIf="photoUrl" class="ion-margin-top">
|
|
<img [src]="photoUrl" alt="Foto tomada">
|
|
</div>
|
|
</ion-content>
|
|
`
|
|
})
|
|
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: `
|
|
<ion-header>
|
|
<ion-toolbar>
|
|
<ion-title>Ubicación</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-content class="ion-padding">
|
|
<ion-button expand="block" (click)="obtenerUbicacion()">
|
|
<ion-icon name="location" slot="start"></ion-icon>
|
|
Obtener Ubicación
|
|
</ion-button>
|
|
|
|
<ion-card *ngIf="coordenadas">
|
|
<ion-card-header>
|
|
<ion-card-title>Tu ubicación actual</ion-card-title>
|
|
</ion-card-header>
|
|
<ion-card-content>
|
|
<p><strong>Latitud:</strong> {{ coordenadas.latitude }}</p>
|
|
<p><strong>Longitud:</strong> {{ coordenadas.longitude }}</p>
|
|
<p><strong>Precisión:</strong> {{ coordenadas.accuracy }} metros</p>
|
|
</ion-card-content>
|
|
</ion-card>
|
|
</ion-content>
|
|
`
|
|
})
|
|
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)
|
|
|