angular-capacitor-patterns — community angular-capacitor-patterns, eleon-client, community, ide skills, Claude Code, Cursor, Windsurf

v1.0.0

Acerca de este Skill

Perfecto para Agentes de Aplicaciones Móviles que necesitan patrones arquitectónicos especializados para aplicaciones móviles híbridas que utilizan Angular Standalone Components y Capacitor 6. Arquitectura y patrones para apps móviles con Angular 18+, Capacitor 6, PrimeNG y GSAP. Úsalo cuando construyas componentes móviles, implementes sincronización offline, o integres plugins de Capacitor.

kizzz kizzz
[0]
[0]
Updated: 3/12/2026

Killer-Skills Review

Decision support comes first. Repository text comes second.

Reference-Only Page Review Score: 7/11

This page remains useful for operators, but Killer-Skills treats it as reference material instead of a primary organic landing page.

Original recommendation layer Concrete use-case guidance Explicit limitations and caution
Review Score
7/11
Quality Score
42
Canonical Locale
es
Detected Body Locale
es

Perfecto para Agentes de Aplicaciones Móviles que necesitan patrones arquitectónicos especializados para aplicaciones móviles híbridas que utilizan Angular Standalone Components y Capacitor 6. Arquitectura y patrones para apps móviles con Angular 18+, Capacitor 6, PrimeNG y GSAP. Úsalo cuando construyas componentes móviles, implementes sincronización offline, o integres plugins de Capacitor.

¿Por qué usar esta habilidad?

Permite a los agentes implementar estrategias de offline-first, integrar plugins de Capacitor como Notificaciones y Almacenamiento, y configurar animaciones GSAP para gamificación, todo mientras estructuran características con NgRx Signals y utilizan Supabase para la sincronización.

Mejor para

Perfecto para Agentes de Aplicaciones Móviles que necesitan patrones arquitectónicos especializados para aplicaciones móviles híbridas que utilizan Angular Standalone Components y Capacitor 6.

Casos de uso accionables for angular-capacitor-patterns

Implementar la sincronización de offline-first con Supabase
Integrar plugins de Capacitor para una funcionalidad móvil mejorada
Configurar animaciones GSAP para elementos de gamificación interactivos
Estructurar características de aplicaciones móviles con NgRx Signals para una mejor gestión de estado

! Seguridad y limitaciones

  • Requiere Angular Standalone Components y Capacitor 6
  • Las estrategias de offline-first pueden requerir una configuración y setup adicionales
  • La integración de Supabase es necesaria para la sincronización de offline

Why this page is reference-only

  • - Current locale does not satisfy the locale-governance contract.
  • - The underlying skill quality score is below the review floor.

Source Boundary

The section below is supporting source material from the upstream repository. Use the Killer-Skills review above as the primary decision layer.

Labs Demo

Browser Sandbox Environment

⚡️ Ready to unleash?

Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.

Boot Container Sandbox

FAQ & Installation Steps

These questions and steps mirror the structured data on this page for better search understanding.

? Frequently Asked Questions

What is angular-capacitor-patterns?

Perfecto para Agentes de Aplicaciones Móviles que necesitan patrones arquitectónicos especializados para aplicaciones móviles híbridas que utilizan Angular Standalone Components y Capacitor 6. Arquitectura y patrones para apps móviles con Angular 18+, Capacitor 6, PrimeNG y GSAP. Úsalo cuando construyas componentes móviles, implementes sincronización offline, o integres plugins de Capacitor.

How do I install angular-capacitor-patterns?

Run the command: npx killer-skills add kizzz/eleon-client/angular-capacitor-patterns. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.

What are the use cases for angular-capacitor-patterns?

Key use cases include: Implementar la sincronización de offline-first con Supabase, Integrar plugins de Capacitor para una funcionalidad móvil mejorada, Configurar animaciones GSAP para elementos de gamificación interactivos, Estructurar características de aplicaciones móviles con NgRx Signals para una mejor gestión de estado.

Which IDEs are compatible with angular-capacitor-patterns?

This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.

Are there any limitations for angular-capacitor-patterns?

Requiere Angular Standalone Components y Capacitor 6. Las estrategias de offline-first pueden requerir una configuración y setup adicionales. La integración de Supabase es necesaria para la sincronización de offline.

How To Install

  1. 1. Open your terminal

    Open the terminal or command line in your project directory.

  2. 2. Run the install command

    Run: npx killer-skills add kizzz/eleon-client/angular-capacitor-patterns. The CLI will automatically detect your IDE or AI agent and configure the skill.

  3. 3. Start using the skill

    The skill is now active. Your AI agent can use angular-capacitor-patterns immediately in the current project.

! Reference-Only Mode

This page remains useful for installation and reference, but Killer-Skills no longer treats it as a primary indexable landing page. Read the review above before relying on the upstream repository instructions.

Imported Repository Instructions

The section below is supporting source material from the upstream repository. Use the Killer-Skills review above as the primary decision layer.

Supporting Evidence

angular-capacitor-patterns

Install angular-capacitor-patterns, an AI agent skill for AI agent workflows and automation. Works with Claude Code, Cursor, and Windsurf with one-command...

SKILL.md
Readonly
Imported Repository Instructions
The section below is supporting source material from the upstream repository. Use the Killer-Skills review above as the primary decision layer.
Supporting Evidence

Angular + Capacitor Mobile Patterns

Patrones arquitectónicos especializados para aplicaciones móviles híbridas usando Angular Standalone Components, Capacitor 6, y estrategias offline-first.

Cuándo Usar Esta Skill

  • Crear componentes móviles optimizados para touch
  • Implementar sincronización offline-first con Supabase
  • Integrar plugins de Capacitor (Notifications, Storage, Camera)
  • Configurar animaciones GSAP para gamificación
  • Estructurar features con NgRx Signals

Principios Fundamentales

1. Mobile-First Architecture

Todos los componentes deben:

  • Usar ChangeDetectionStrategy.OnPush obligatorio
  • Implementar gestos touch-friendly (min 44x44px)
  • Considerar keyboard virtual (bottom padding dinámico)
  • Optimizar para 3G/4G (lazy loading agresivo)
typescript
1@Component({ 2 selector: 'app-block-card', 3 standalone: true, 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 host: { 6 '[style.min-height.px]': '88', // 2x touch target 7 '[class.ios]': 'platform.is("ios")', 8 '[class.android]': 'platform.is("android")' 9 } 10}) 11export class BlockCardComponent {}

2. Offline-First Data Flow

┌─────────────────────────────────────────────┐
│ User Action (Complete Block)               │
└───────────────┬─────────────────────────────┘
                │
                ▼
┌───────────────────────────────────────────┐
│ 1. Write to Local SQLite FIRST            │
│    (Instant UI update via signals)        │
└───────────────┬───────────────────────────┘
                │
                ▼
┌───────────────────────────────────────────┐
│ 2. Queue Sync Operation                   │
│    (Background service)                   │
└───────────────┬───────────────────────────┘
                │
                ▼
         ┌──────┴───────┐
         │ Online?      │
         └──┬────────┬──┘
           YES      NO
            │        │
            │        └─> Store in sync_queue
            │            (retry on reconnect)
            ▼
┌───────────────────────────────────────────┐
│ 3. Push to Supabase                       │
│    (Last-write-wins conflict resolution)  │
└───────────────┬───────────────────────────┘
                │
                ▼
┌───────────────────────────────────────────┐
│ 4. Update Local with server response      │
│    (Reconcile timestamps)                 │
└───────────────────────────────────────────┘

3. NgRx Signals State Management

NUNCA usar NgRx Store clásico. Siempre Signals:

typescript
1// ✅ CORRECTO: Signal Store con computeds 2import { signalStore, withState, withComputed, withMethods } from '@ngrx/signals'; 3 4interface BlocksState { 5 blocks: DailyBlock[]; 6 selectedDate: string; 7 loading: boolean; 8} 9 10export const BlocksStore = signalStore( 11 { providedIn: 'root' }, 12 withState<BlocksState>({ 13 blocks: [], 14 selectedDate: new Date().toISOString().split('T')[0], 15 loading: false 16 }), 17 18 withComputed(({ blocks, selectedDate }) => ({ 19 todayBlocks: computed(() => 20 blocks().filter(b => b.date === selectedDate()) 21 ), 22 completedCount: computed(() => 23 blocks().filter(b => b.status === 'completed').length 24 ), 25 completionRate: computed(() => { 26 const total = blocks().length; 27 const completed = blocks().filter(b => b.status === 'completed').length; 28 return total > 0 ? (completed / total) * 100 : 0; 29 }) 30 })), 31 32 withMethods((store, blocksRepo = inject(BlocksRepository)) => ({ 33 async loadBlocks(userId: string, date: string) { 34 patchState(store, { loading: true }); 35 try { 36 const blocks = await blocksRepo.getDailyBlocks(userId, date); 37 patchState(store, { blocks, selectedDate: date, loading: false }); 38 } catch (error) { 39 console.error('Failed to load blocks', error); 40 patchState(store, { loading: false }); 41 } 42 }, 43 44 async completeBlock(blockId: string, note?: string) { 45 // Optimistic update 46 const updatedBlocks = store.blocks().map(b => 47 b.id === blockId 48 ? { ...b, status: 'completed' as const, completion_note: note } 49 : b 50 ); 51 patchState(store, { blocks: updatedBlocks }); 52 53 // Background sync 54 try { 55 await blocksRepo.completeBlock(blockId, note); 56 } catch (error) { 57 // Rollback on failure 58 patchState(store, { blocks: store.blocks() }); 59 throw error; 60 } 61 } 62 })) 63);

Capacitor Integration Patterns

Plugin Initialization

typescript
1// src/app/core/capacitor/capacitor.service.ts 2import { Injectable, inject } from '@angular/core'; 3import { Platform } from '@angular/cdk/platform'; 4import { App } from '@capacitor/app'; 5import { StatusBar, Style } from '@capacitor/status-bar'; 6import { SplashScreen } from '@capacitor/splash-screen'; 7 8@Injectable({ providedIn: 'root' }) 9export class CapacitorService { 10 private platform = inject(Platform); 11 12 async initialize() { 13 if (!this.platform.isBrowser) { 14 await this.setupStatusBar(); 15 await this.setupAppListeners(); 16 await SplashScreen.hide(); 17 } 18 } 19 20 private async setupStatusBar() { 21 if (this.platform.IOS) { 22 await StatusBar.setStyle({ style: Style.Dark }); 23 } else if (this.platform.ANDROID) { 24 await StatusBar.setBackgroundColor({ color: '#1a202c' }); 25 } 26 } 27 28 private async setupAppListeners() { 29 App.addListener('appStateChange', ({ isActive }) => { 30 if (isActive) { 31 // Resume sync cuando app vuelve a foreground 32 inject(SyncService).resumeSync(); 33 } 34 }); 35 36 App.addListener('backButton', ({ canGoBack }) => { 37 if (!canGoBack) { 38 App.exitApp(); 39 } 40 }); 41 } 42}

Local Notifications Pattern

typescript
1// src/app/core/notifications/notification.service.ts 2import { LocalNotifications, ScheduleOptions } from '@capacitor/local-notifications'; 3import { Haptics, ImpactStyle } from '@capacitor/haptics'; 4 5@Injectable({ providedIn: 'root' }) 6export class NotificationService { 7 private hasPermissions = signal(false); 8 9 async init() { 10 const result = await LocalNotifications.requestPermissions(); 11 this.hasPermissions.set(result.display === 'granted'); 12 } 13 14 async scheduleBlockReminder(block: DailyBlock, minutesBefore: number = 5) { 15 if (!this.hasPermissions()) return; 16 17 const reminderTime = new Date(block.start_time); 18 reminderTime.setMinutes(reminderTime.getMinutes() - minutesBefore); 19 20 await LocalNotifications.schedule({ 21 notifications: [{ 22 id: this.generateNotificationId(block.id), 23 title: `Próximo: ${block.name}`, 24 body: `Comienza en ${minutesBefore} minutos`, 25 schedule: { at: reminderTime }, 26 actionTypeId: 'BLOCK_REMINDER', 27 extra: { blockId: block.id, blockName: block.name } 28 }] 29 }); 30 } 31 32 // Cancelar todas las notificaciones de un día específico 33 async cancelDayNotifications(date: string) { 34 const pending = await LocalNotifications.getPending(); 35 const idsToCancel = pending.notifications 36 .filter(n => n.extra?.date === date) 37 .map(n => n.id); 38 39 if (idsToCancel.length > 0) { 40 await LocalNotifications.cancel({ notifications: idsToCancel.map(id => ({ id })) }); 41 } 42 } 43 44 // Haptic feedback para confirmaciones 45 async triggerSuccess() { 46 await Haptics.impact({ style: ImpactStyle.Medium }); 47 } 48 49 async triggerError() { 50 await Haptics.notification({ type: NotificationType.Error }); 51 } 52 53 private generateNotificationId(blockId: string): number { 54 // Convertir UUID a número único (primeros 8 chars en hex) 55 return parseInt(blockId.slice(0, 8), 16); 56 } 57}

PrimeNG Mobile Optimizations

Touch-Optimized Dialog

typescript
1// Wrapper component para dialogs móviles 2@Component({ 3 selector: 'app-mobile-dialog', 4 standalone: true, 5 imports: [DialogModule], 6 template: ` 7 <p-dialog 8 [(visible)]="visible" 9 [modal]="true" 10 [dismissableMask]="true" 11 [blockScroll]="true" 12 [styleClass]="'mobile-dialog'" 13 [position]="position()" 14 [breakpoints]="{ '960px': '90vw', '640px': '100vw' }" 15 > 16 <ng-content></ng-content> 17 </p-dialog> 18 `, 19 styles: [` 20 :host ::ng-deep .mobile-dialog { 21 .p-dialog-content { 22 padding: var(--spacing-lg); 23 max-height: 70vh; 24 overflow-y: auto; 25 -webkit-overflow-scrolling: touch; 26 } 27 28 // iOS safe area 29 @supports (padding: env(safe-area-inset-bottom)) { 30 .p-dialog-footer { 31 padding-bottom: calc(var(--spacing-md) + env(safe-area-inset-bottom)); 32 } 33 } 34 } 35 `] 36}) 37export class MobileDialogComponent { 38 visible = model<boolean>(false); 39 40 private platform = inject(Platform); 41 42 position = computed(() => 43 this.platform.IOS ? 'bottom' : 'center' 44 ); 45}

Infinite Scroll con DataView

typescript
1@Component({ 2 selector: 'app-habit-history', 3 standalone: true, 4 imports: [DataViewModule, ScrollerModule], 5 template: ` 6 <p-dataView 7 [value]="visibleItems()" 8 [layout]="'list'" 9 [lazy]="true" 10 (onLazyLoad)="loadMore($event)" 11 > 12 <ng-template let-item pTemplate="listItem"> 13 <app-habit-card [habit]="item" /> 14 </ng-template> 15 </p-dataView> 16 ` 17}) 18export class HabitHistoryComponent { 19 private habitsStore = inject(HabitsStore); 20 21 visibleItems = computed(() => 22 this.habitsStore.habits().slice(0, this.pageSize() * this.currentPage()) 23 ); 24 25 private pageSize = signal(20); 26 private currentPage = signal(1); 27 28 loadMore(event: any) { 29 this.currentPage.update(p => p + 1); 30 } 31}

GSAP Animation Patterns

Streak Celebration Animation

typescript
1// src/app/shared/animations/streak.animations.ts 2import gsap from 'gsap'; 3 4export class StreakAnimations { 5 static playStreakComplete(element: HTMLElement, days: number) { 6 const tl = gsap.timeline(); 7 8 // Scale + Rotation 9 tl.to(element, { 10 scale: 1.3, 11 rotation: 360, 12 duration: 0.6, 13 ease: 'back.out(2)', 14 onStart: () => { 15 element.classList.add('celebrating'); 16 } 17 }); 18 19 // Bounce back 20 tl.to(element, { 21 scale: 1, 22 duration: 0.3, 23 ease: 'elastic.out(1, 0.5)' 24 }); 25 26 // Milestone confetti 27 if ([7, 30, 100].includes(days)) { 28 this.playConfetti(element.parentElement!); 29 } 30 } 31 32 static playStreakLost(element: HTMLElement) { 33 const tl = gsap.timeline(); 34 35 // Shake violently 36 tl.to(element, { 37 x: -10, 38 duration: 0.05, 39 repeat: 5, 40 yoyo: true 41 }); 42 43 // Fade out and shrink 44 tl.to(element, { 45 opacity: 0, 46 scale: 0.5, 47 duration: 0.4, 48 ease: 'power2.in', 49 onComplete: () => { 50 gsap.set(element, { opacity: 1, scale: 1, x: 0 }); 51 } 52 }); 53 } 54 55 private static playConfetti(container: HTMLElement) { 56 // Crear partículas 57 const colors = ['#6366f1', '#10b981', '#f59e0b', '#ef4444']; 58 const particleCount = 30; 59 60 for (let i = 0; i < particleCount; i++) { 61 const particle = document.createElement('div'); 62 particle.className = 'confetti-particle'; 63 particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; 64 container.appendChild(particle); 65 66 gsap.fromTo(particle, 67 { 68 x: 0, 69 y: 0, 70 opacity: 1, 71 scale: 1 72 }, 73 { 74 x: (Math.random() - 0.5) * 300, 75 y: -200 + Math.random() * 100, 76 opacity: 0, 77 scale: 0, 78 duration: 1.5, 79 ease: 'power2.out', 80 onComplete: () => particle.remove() 81 } 82 ); 83 } 84 } 85}

Page Transition Animation

typescript
1// src/app/core/animations/route-animations.ts 2import { trigger, transition, style, animate, query } from '@angular/animations'; 3 4export const slideInAnimation = trigger('routeAnimations', [ 5 transition('* <=> *', [ 6 query(':enter, :leave', [ 7 style({ 8 position: 'absolute', 9 width: '100%', 10 transform: 'translateX(0)', 11 opacity: 1 12 }) 13 ], { optional: true }), 14 15 query(':enter', [ 16 style({ transform: 'translateX(100%)', opacity: 0 }) 17 ], { optional: true }), 18 19 query(':leave', [ 20 animate('300ms ease-out', style({ 21 transform: 'translateX(-100%)', 22 opacity: 0 23 })) 24 ], { optional: true }), 25 26 query(':enter', [ 27 animate('300ms 150ms ease-out', style({ 28 transform: 'translateX(0)', 29 opacity: 1 30 })) 31 ], { optional: true }) 32 ]) 33]);

Offline Sync Patterns

SQLite Repository

typescript
1// src/app/core/database/sqlite.service.ts 2import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite'; 3 4@Injectable({ providedIn: 'root' }) 5export class SQLiteService { 6 private sqlite: SQLiteConnection; 7 private db!: SQLiteDBConnection; 8 9 async init() { 10 this.sqlite = new SQLiteConnection(CapacitorSQLite); 11 12 this.db = await this.sqlite.createConnection( 13 'lifeblocks', 14 false, 15 'no-encryption', 16 1, 17 false 18 ); 19 20 await this.db.open(); 21 await this.createTables(); 22 } 23 24 private async createTables() { 25 const schema = ` 26 CREATE TABLE IF NOT EXISTS daily_blocks ( 27 id TEXT PRIMARY KEY, 28 user_id TEXT NOT NULL, 29 date TEXT NOT NULL, 30 name TEXT NOT NULL, 31 start_time TEXT NOT NULL, 32 end_time TEXT NOT NULL, 33 status TEXT DEFAULT 'pending', 34 completion_note TEXT, 35 completed_at TEXT, 36 synced INTEGER DEFAULT 0, 37 updated_at TEXT DEFAULT CURRENT_TIMESTAMP 38 ); 39 40 CREATE INDEX IF NOT EXISTS idx_blocks_date ON daily_blocks(date); 41 CREATE INDEX IF NOT EXISTS idx_blocks_sync ON daily_blocks(synced); 42 43 CREATE TABLE IF NOT EXISTS sync_queue ( 44 id INTEGER PRIMARY KEY AUTOINCREMENT, 45 operation TEXT NOT NULL, 46 table_name TEXT NOT NULL, 47 record_id TEXT NOT NULL, 48 payload TEXT NOT NULL, 49 created_at TEXT DEFAULT CURRENT_TIMESTAMP 50 ); 51 `; 52 53 await this.db.execute(schema); 54 } 55 56 async getUnsyncedBlocks(): Promise<DailyBlock[]> { 57 const result = await this.db.query( 58 'SELECT * FROM daily_blocks WHERE synced = 0' 59 ); 60 return result.values || []; 61 } 62 63 async markAsSynced(blockId: string) { 64 await this.db.run( 65 'UPDATE daily_blocks SET synced = 1 WHERE id = ?', 66 [blockId] 67 ); 68 } 69}

Background Sync Service

typescript
1// src/app/core/sync/background-sync.service.ts 2@Injectable({ providedIn: 'root' }) 3export class BackgroundSyncService { 4 private sqlite = inject(SQLiteService); 5 private supabase = inject(SupabaseService); 6 private network = inject(NetworkService); 7 8 private syncInterval$ = interval(30000); // 30 segundos 9 10 startAutoSync() { 11 this.syncInterval$ 12 .pipe( 13 filter(() => this.network.isOnline()), 14 switchMap(() => this.syncPendingChanges()), 15 catchError(error => { 16 console.error('Sync failed', error); 17 return of(null); 18 }) 19 ) 20 .subscribe(); 21 } 22 23 async syncPendingChanges() { 24 const unsynced = await this.sqlite.getUnsyncedBlocks(); 25 26 for (const block of unsynced) { 27 try { 28 await this.supabase 29 .from('daily_blocks') 30 .upsert(block, { onConflict: 'id' }); 31 32 await this.sqlite.markAsSynced(block.id); 33 } catch (error) { 34 console.error(`Failed to sync block ${block.id}`, error); 35 // Continuar con el siguiente 36 } 37 } 38 } 39}

Performance Optimization

Virtual Scrolling for Long Lists

typescript
1@Component({ 2 selector: 'app-inventory-list', 3 standalone: true, 4 imports: [ScrollingModule, VirtualScrollerModule], 5 template: ` 6 <cdk-virtual-scroll-viewport itemSize="72" class="viewport"> 7 <app-inventory-item 8 *cdkVirtualFor="let item of items(); trackBy: trackById" 9 [item]="item" 10 /> 11 </cdk-virtual-scroll-viewport> 12 ` 13}) 14export class InventoryListComponent { 15 items = input.required<InventoryItem[]>(); 16 17 trackById(index: number, item: InventoryItem) { 18 return item.id; 19 } 20}

Image Loading Strategy

typescript
1// Directiva para lazy loading de imágenes 2@Directive({ 3 selector: 'img[appLazyLoad]', 4 standalone: true 5}) 6export class LazyLoadDirective implements OnInit { 7 @Input() src!: string; 8 private el = inject(ElementRef<HTMLImageElement>); 9 10 ngOnInit() { 11 if ('IntersectionObserver' in window) { 12 const observer = new IntersectionObserver((entries) => { 13 entries.forEach(entry => { 14 if (entry.isIntersecting) { 15 this.loadImage(); 16 observer.disconnect(); 17 } 18 }); 19 }); 20 21 observer.observe(this.el.nativeElement); 22 } else { 23 this.loadImage(); 24 } 25 } 26 27 private loadImage() { 28 this.el.nativeElement.src = this.src; 29 } 30}

Testing Patterns

Component Testing with Signals

typescript
1import { ComponentFixture, TestBed } from '@angular/core/testing'; 2import { signal } from '@angular/core'; 3 4describe('BlockCardComponent', () => { 5 let component: BlockCardComponent; 6 let fixture: ComponentFixture<BlockCardComponent>; 7 8 beforeEach(async () => { 9 await TestBed.configureTestingModule({ 10 imports: [BlockCardComponent] 11 }).compileComponents(); 12 13 fixture = TestBed.createComponent(BlockCardComponent); 14 component = fixture.componentInstance; 15 }); 16 17 it('should update completion status when confirmed', async () => { 18 const mockBlock = { 19 id: '1', 20 name: 'Deep Work', 21 status: 'pending' as const 22 }; 23 24 fixture.componentRef.setInput('block', mockBlock); 25 fixture.detectChanges(); 26 27 // Simular confirmación 28 await component.confirmCompletion('Terminé el módulo de auth'); 29 30 expect(component.block().status).toBe('completed'); 31 }); 32});

Service Testing with Mocks

typescript
1describe('BlocksRepository', () => { 2 let repo: BlocksRepository; 3 let mockSupabase: jasmine.SpyObj<SupabaseClient>; 4 5 beforeEach(() => { 6 mockSupabase = jasmine.createSpyObj('SupabaseClient', ['from']); 7 8 TestBed.configureTestingModule({ 9 providers: [ 10 BlocksRepository, 11 { provide: SupabaseService, useValue: { client: mockSupabase } } 12 ] 13 }); 14 15 repo = TestBed.inject(BlocksRepository); 16 }); 17 18 it('should fetch daily blocks', async () => { 19 const mockData = [{ id: '1', name: 'Gym' }]; 20 mockSupabase.from.and.returnValue({ 21 select: () => ({ 22 eq: () => ({ 23 eq: () => Promise.resolve({ data: mockData, error: null }) 24 }) 25 }) 26 } as any); 27 28 const result = await repo.getDailyBlocks('user-1', '2025-01-30'); 29 30 expect(result).toEqual(mockData); 31 }); 32});

Troubleshooting Common Issues

Issue: Notificaciones no aparecen en iOS

Causa: Permisos no solicitados correctamente

Solución:

typescript
1// Solicitar permisos DESPUÉS de que el usuario interactúe 2async requestNotificationPermissions() { 3 const result = await LocalNotifications.requestPermissions(); 4 5 if (result.display !== 'granted') { 6 // Mostrar dialog explicando por qué son necesarias 7 this.showPermissionsExplanation(); 8 } 9}

Issue: App se congela en sincronización

Causa: Operaciones síncronas en main thread

Solución: Usar Web Workers

typescript
1// sync.worker.ts 2addEventListener('message', async (e) => { 3 const { blocks } = e.data; 4 5 // Procesamiento pesado aquí 6 const processed = await heavySync(blocks); 7 8 postMessage({ result: processed }); 9});

Issue: Memoria creciente en listas largas

Causa: No usar virtual scrolling

Solución: Implementar cdk-virtual-scroll-viewport (ver sección Performance)


Scripts de Automatización

Generar Componente Mobile-Ready

bash
1#!/bin/bash 2# scripts/generate-mobile-component.sh 3 4COMPONENT_NAME=$1 5 6ng generate component "features/${COMPONENT_NAME}" \ 7 --standalone=true \ 8 --change-detection=OnPush \ 9 --skip-tests=false \ 10 --style=scss 11 12echo "✅ Componente mobile-ready creado en features/${COMPONENT_NAME}"

Sincronizar Tipos de Supabase

bash
1#!/bin/bash 2# scripts/sync-supabase-types.sh 3 4npx supabase gen types typescript \ 5 --project-id $SUPABASE_PROJECT_ID \ 6 > src/app/core/supabase/database.types.ts 7 8echo "✅ Tipos de Supabase actualizados"

Checklist de Code Review

Antes de merge, verificar:

  • Todos los componentes usan ChangeDetectionStrategy.OnPush
  • State management usa NgRx Signals (no NgRx Store)
  • Sincronización escribe primero a SQLite, luego a Supabase
  • Notificaciones tienen haptic feedback
  • Touch targets mínimo 44x44px
  • Animaciones GSAP optimizadas (willChange usado correctamente)
  • Tests cubren casos offline y online
  • Safe area insets considerados para iOS

Related skills: angular-primeng, angular-best-practices-primeng (PrimeNG); angular (Signals, Standalone).


Versión: 1.0.0
Última actualización: 2025-01-30
Compatibilidad: Angular 18+, Capacitor 6+, PrimeNG 18+

Habilidades relacionadas

Looking for an alternative to angular-capacitor-patterns or another community skill for your workflow? Explore these related open-source skills.

Ver todo

openclaw-release-maintainer

Logo of openclaw
openclaw

Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

333.8k
0
Inteligencia Artificial

widget-generator

Logo of f
f

Generate customizable widget plugins for the prompts.chat feed system

149.6k
0
Inteligencia Artificial

flags

Logo of vercel
vercel

The React Framework

138.4k
0
Navegador

pr-review

Logo of pytorch
pytorch

Tensors and Dynamic neural networks in Python with strong GPU acceleration

98.6k
0
Desarrollador