mcpField
C
capacitor-angular

capacitor-angular

github.com/capawesome-team开发521.1k 次安装1 周前更新
代码仓库

关于 capacitor-angular

为使用 Angular 开发 Capacitor 应用的开发者提供项目结构、插件和平台集成指导,适用于 Angular 16+ 与 Capacitor 6、7、8。内容涵盖 NgZone、生命周期、深层链接及 Android 返回键等 Angular 特有模式。

主要功能

  1. 01检测 Angular 架构与 Capacitor 配置
  2. 02将 Capacitor 添加到 Angular 项目
  3. 03使用服务封装 Capacitor 插件
  4. 04通过 NgZone 更新界面状态
  5. 05管理监听器与应用生命周期
  6. 06处理平台检测、深层链接和返回键

适用场景

  1. 01在 Angular 项目中添加 Capacitor
  2. 02集成并管理 Capacitor 插件
  3. 03修复插件事件触发后界面不更新
  4. 04为移动应用配置深层链接和返回键

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/capawesome-team/skills --skill capacitor-angular

安装后在 Claude Code 中输入/capacitor-angular确认已加载。

详细介绍

使用 Angular 的 Capacitor

面向 Angular 的 Capacitor 应用开发模式和最佳实践——项目结构、服务、生命周期钩子、NgZone 集成和插件使用。

前提条件

  1. Capacitor 6、7 或 8 应用,并使用 Angular 16+。
  2. 已安装 Node.js 和 npm。
  3. 已安装 Angular CLI(npm install -g @angular/cli)。
  4. 对于 iOS:已安装 Xcode。
  5. 对于 Android:已安装 Android Studio。

Agent 行为

  • 先自动检测,再提问。 检查项目中的 angular.json、package.json、capacitor.config.ts 或 capacitor.config.json,以及现有目录结构。仅在无法检测到信息时向用户提问。
  • 逐步引导。 一次引导用户完成一个步骤。
  • 适应项目风格。 检测项目使用的是独立组件还是基于 NgModule 的架构,并据此调整代码示例。

操作步骤

步骤 1:分析项目

通过读取项目文件自动检测以下信息:

  1. Angular 版本:从 package.json 中读取 @angular/core 版本。
  2. Capacitor 版本:从 package.json 中读取 @capacitor/core 版本。如果不存在,则表示尚未添加 Capacitor——继续执行步骤 2。
  3. 架构风格:检查 src/main.ts,判断使用的是 bootstrapApplication(独立组件)还是 platformBrowserDynamic().bootstrapModule(NgModule)。检查 angular.json 以进一步确认。
  4. 平台:检查哪些目录存在(android/、ios/)。
  5. Capacitor 配置格式:检查是否存在 capacitor.config.ts(TypeScript)或 capacitor.config.json(JSON)。
  6. 构建输出目录:从 angular.json 中 projects > <project-name> > architect > build > options > outputPath 读取 outputPath。设置 Capacitor 的 webDir 时需要此路径。
步骤 2:将 Capacitor 添加到 Angular 项目

如果 package.json 中已包含 @capacitor/core,则跳过此步骤。

  1. 安装 Capacitor 核心库和 CLI:

    npm install @capacitor/core
    npm install -D @capacitor/cli
    
  2. 初始化 Capacitor:

    npx cap init
    

    出现提示时,将网页目录设置为步骤 1 中检测到的 Angular 构建输出路径。对于使用应用构建器的 Angular 17+,通常为 dist/<project-name>/browser。对于较旧的 Angular 版本,通常为 dist/<project-name>。

  3. 确认生成的 capacitor.config.ts 或 capacitor.config.json 中的 webDir 值与 Angular 构建输出路径一致。如果不一致,请更新:

    capacitor.config.ts:

    import type { CapacitorConfig } from '@capacitor/cli';
    
    const config: CapacitorConfig = {
      appId: 'com.example.app',
      appName: 'my-app',
      webDir: 'dist/my-app/browser',
    };
    
    export default config;
    

    capacitor.config.json:

    {
      "appId": "com.example.app",
      "appName": "my-app",
      "webDir": "dist/my-app/browser"
    }
    
  4. 构建 Angular 应用并添加平台:

    ng build
    npm install @capacitor/android @capacitor/ios
    npx cap add android
    npx cap add ios
    npx cap sync
    
步骤 3:项目结构

Capacitor Angular 项目的结构如下:

my-app/
├── android/                  # Android native project
├── ios/                      # iOS native project
├── src/
│   ├── app/
│   │   ├── app.component.ts
│   │   ├── app.config.ts     # Standalone: app configuration
│   │   ├── app.module.ts     # NgModule: root module
│   │   ├── app.routes.ts     # Routing configuration
│   │   └── services/         # Angular services for Capacitor plugins
│   ├── environments/
│   │   ├── environment.ts
│   │   └── environment.prod.ts
│   ├── index.html
│   └── main.ts
├── angular.json
├── capacitor.config.ts       # or capacitor.config.json
├── package.json
└── tsconfig.json

要点:

  • android/ 和 ios/ 目录包含原生项目,应提交到版本控制中。
  • src/ 目录包含 Angular 应用,是 Capacitor 应用的网页层。
  • 在 src/app/ 中的 Angular 服务或组件内调用 Capacitor 插件。
步骤 4:在 Angular 中使用 Capacitor 插件

Capacitor 插件是纯 TypeScript API。可在 Angular 组件或服务中直接导入并调用。

在组件中直接使用
import { Component } from '@angular/core';
import { Geolocation } from '@capacitor/geolocation';

@Component({
  selector: 'app-location',
  template: `
    <div>
      <p>Latitude: {{ latitude }}</p>
      <p>Longitude: {{ longitude }}</p>
      <button (click)="getCurrentPosition()">Get Location</button>
    </div>
  `,
  standalone: true,
})
export class LocationComponent {
  latitude: number | null = null;
  longitude: number | null = null;

  async getCurrentPosition() {
    const position = await Geolocation.getCurrentPosition();
    this.latitude = position.coords.latitude;
    this.longitude = position.coords.longitude;
  }
}
将插件封装在 Angular 服务中(推荐)

将 Capacitor 插件封装在 Angular 服务中,可提供依赖注入和可测试性,并集中处理平台差异:

import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Capacitor } from '@capacitor/core';

@Injectable({
  providedIn: 'root',
})
export class CameraService {
  async takePhoto(): Promise<Photo> {
    return Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Uri,
      source: CameraSource.Camera,
    });
  }

  async pickFromGallery(): Promise<Photo> {
    return Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Uri,
      source: CameraSource.Photos,
    });
  }

  isNativePlatform(): boolean {
    return Capacitor.isNativePlatform();
  }
}

在组件中使用该服务:

import { Component, inject } from '@angular/core';
import { CameraService } from '../services/camera.service';

@Component({
  selector: 'app-photo',
  template: `
    <button (click)="takePhoto()">Take Photo</button>
    <img *ngIf="photoUrl" [src]="photoUrl" alt="Captured photo" />
  `,
  standalone: true,
})
export class PhotoComponent {
  private cameraService = inject(CameraService);
  photoUrl: string | null = null;

  async takePhoto() {
    const photo = await this.cameraService.takePhoto();
    this.photoUrl = photo.webPath ?? null;
  }
}
步骤 5:用于插件事件监听器的 NgZone 集成

Capacitor 插件事件监听器在 Angular 的 NgZone 执行上下文之外运行。当插件监听器更新组件状态时,Angular 的变更检测不会自动触发。将处理逻辑封装在 NgZone.run() 中即可解决此问题。

不使用 NgZone(错误——界面不会更新):

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Network, ConnectionStatus } from '@capacitor/network';
import { PluginListenerHandle } from '@capacitor/core';

@Component({
  selector: 'app-network',
  template: `<p>Status: {{ networkStatus }}</p>`,
  standalone: true,
})
export class NetworkComponent implements OnInit, OnDestroy {
  networkStatus = 'Unknown';
  private listenerHandle: PluginListenerHandle | null = null;

  async ngOnInit() {
    // BUG: This callback runs outside NgZone — the template will not update.
    this.listenerHandle = await Network.addListener('networkStatusChange', (status) => {
      this.networkStatus = status.connected ? 'Online' : 'Offline';
    });
  }

  async ngOnDestroy() {
    await this.listenerHandle?.remove();
  }
}

使用 NgZone(正确——界面会正常更新):

import { Component, NgZone, OnInit, OnDestroy, inject } from '@angular/core';
import { Network, ConnectionStatus } from '@capacitor/network';
import { PluginListenerHandle } from '@capacitor/core';

@Component({
  selector: 'app-network',
  template: `<p>Status: {{ networkStatus }}</p>`,
  standalone: true,
})
export class NetworkComponent implements OnInit, OnDestroy {
  private ngZone = inject(NgZone);
  networkStatus = 'Unknown';
  private listenerHandle: PluginListenerHandle | null = null;

  async ngOnInit() {
    this.listenerHandle = await Network.addListener('networkStatusChange', (status) => {
      this.ngZone.run(() => {
        this.networkStatus = status.connected ? 'Online' : 'Offline';
      });
    });
  }

  async ngOnDestroy() {
    await this.listenerHandle?.remove();
  }
}

**规则:**在会更新绑定到模板的组件或服务状态的 Capacitor 插件事件监听器回调中,始终使用 NgZone.run()。

步骤 6:生命周期钩子模式

使用 Angular 生命周期钩子管理 Capacitor 插件监听器。在 ngOnInit 中注册监听器,并在 ngOnDestroy 中移除它们,以防止内存泄漏。

基于服务的监听器管理

对于应用级监听器(例如网络状态、应用状态),请使用在应用启动时初始化的服务:

import { Injectable, NgZone, OnDestroy, inject } from '@angular/core';
import { App } from '@capacitor/app';
import { PluginListenerHandle } from '@capacitor/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AppStateService implements OnDestroy {
  private ngZone = inject(NgZone);
  private listenerHandle: PluginListenerHandle | null = null;

  private isActiveSubject = new BehaviorSubject<boolean>(true);
  isActive$ = this.isActiveSubject.asObservable();

  constructor() {
    this.initListener();
  }

  private async initListener() {
    this.listenerHandle = await App.addListener('appStateChange', (state) => {
      this.ngZone.run(() => {
        this.isActiveSubject.next(state.isActive);
      });
    });
  }

  async ngOnDestroy() {
    await this.listenerHandle?.remove();
  }
}

在应用启动时初始化服务,确保它立即运行。在独立式应用中,使用 APP_INITIALIZER 或在根组件中注入它。在 NgModule 应用中,在 AppComponent 中注入它:

独立式(app.config.ts):

import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { AppStateService } from './services/app-state.service';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: (appStateService: AppStateService) => () => {},
      deps: [AppStateService],
      multi: true,
    },
  ],
};

NgModule(app.component.ts):

import { Component } from '@angular/core';
import { AppStateService } from './services/app-state.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  constructor(private appStateService: AppStateService) {}
}
步骤 7:平台检测

使用 Capacitor.isNativePlatform() 和 Capacitor.getPlatform() 有条件地运行仅限原生平台的代码:

import { Injectable } from '@angular/core';
import { Capacitor } from '@capacitor/core';

@Injectable({
  providedIn: 'root',
})
export class PlatformService {
  isNative(): boolean {
    return Capacitor.isNativePlatform();
  }

  getPlatform(): 'web' | 'ios' | 'android' {
    return Capacitor.getPlatform() as 'web' | 'ios' | 'android';
  }

  isIos(): boolean {
    return Capacitor.getPlatform() === 'ios';
  }

  isAndroid(): boolean {
    return Capacitor.getPlatform() === 'android';
  }

  isWeb(): boolean {
    return Capacitor.getPlatform() === 'web';
  }
}

在组件中使用它来显示/隐藏仅限原生平台的功能:

import { Component, inject } from '@angular/core';
import { PlatformService } from '../services/platform.service';

@Component({
  selector: 'app-settings',
  template: `
    @if (platformService.isNative()) {
      <button (click)="openNativeSettings()">Open Device Settings</button>
    }
  `,
  standalone: true,
})
export class SettingsComponent {
  platformService = inject(PlatformService);

  openNativeSettings() {
    // Native-only logic
  }
}
步骤 8:深层链接路由

通过将 Capacitor 的 App.addListener('appUrlOpen', ...) 事件映射到 Angular Router 导航来处理深层链接:

import { Injectable, NgZone, inject } from '@angular/core';
import { Router } from '@angular/router';
import { App } from '@capacitor/app';

@Injectable({
  providedIn: 'root',
})
export class DeepLinkService {
  private ngZone = inject(NgZone);
  private router = inject(Router);

  constructor() {
    this.initDeepLinkListener();
  }

  private async initDeepLinkListener() {
    await App.addListener('appUrlOpen', (event) => {
      this.ngZone.run(() => {
        const url = new URL(event.url);
        const path = url.pathname;

        // Navigate to the route matching the deep link path.
        // Adjust the path parsing logic to match the app's URL scheme.
        if (path) {
          this.router.navigateByUrl(path);
        }
      });
    });
  }
}

在应用启动时初始化 DeepLinkService(与步骤 6 的模式相同——通过 APP_INITIALIZER 或在根组件中注入)。

步骤 9:返回按钮处理(Android)

使用 App.addListener('backButton', ...) 处理 Android 硬件返回按钮:

import { Injectable, NgZone, inject } from '@angular/core';
import { Location } from '@angular/common';
import { App } from '@capacitor/app';
import { Capacitor } from '@capacitor/core';

@Injectable({
  providedIn: 'root',
})
export class BackButtonService {
  private ngZone = inject(NgZone);
  private location = inject(Location);

  constructor() {
    if (Capacitor.getPlatform() === 'android') {
      this.initBackButtonListener();
    }
  }

  private async initBackButtonListener() {
    await App.addListener('backButton', ({ canGoBack }) => {
      this.ngZone.run(() => {
        if (canGoBack) {
          this.location.back();
        } else {
          App.exitApp();
        }
      });
    });
  }
}
步骤 10:构建和同步工作流

对 Angular 应用进行更改后,构建并同步到原生平台:

ng build
npx cap sync

要在设备或模拟器上运行:

npx cap run android
npx cap run ios

要打开原生 IDE 进行高级配置或调试:

npx cap open android
npx cap open ios

要在开发期间启用实时重载:

npx cap run android --livereload --external
npx cap run ios --livereload --external

这会在内部启动 ng serve,并配置原生应用从开发服务器加载内容。

错误处理

  • 插件监听器未更新 UI:将监听器回调主体包装在 NgZone.run(() => { ... }) 中。这是 Capacitor 中最常见的 Angular 特有问题。
  • webDir 不匹配:如果 npx cap sync 复制了错误的文件,请确认 capacitor.config.ts 或 capacitor.config.json 中的 webDir 与 Angular 构建输出路径一致。对于使用 application builder 的 Angular 17+,路径为 dist/<project-name>/browser。对于较旧的 Angular 版本,路径为 dist/<project-name>。
  • 运行时未找到插件:安装任何新插件后运行 npx cap sync。确认插件已出现在 package.json 依赖项中。
  • 监听器导致内存泄漏:务必在 ngOnDestroy 中移除插件监听器。存储 addListener 返回的 PluginListenerHandle,并在销毁时调用 handle.remove()。
  • 深层链接无法使用:确认应用 URL scheme / 通用链接已在原生项目中配置(Android 对应 android/app/src/main/AndroidManifest.xml,iOS 对应 ios/App/App/Info.plist 和关联域名权限)。确认 DeepLinkService 已在应用启动时初始化。
  • 返回按钮意外关闭应用:确保返回按钮监听器在调用 App.exitApp() 前检查 canGoBack。仅在没有导航历史时退出。
  • ng build 后构建输出为空:确认 angular.json 中的 outputPath 正确。对于 Angular 17+,使用 application builder 时,默认路径已更改为 dist/<project-name>/browser。

相关技能

  • capacitor-app-creation — 从头创建新的 Capacitor 应用。
  • capacitor-app-development — 通用 Capacitor 开发指南,不特定于 Angular。
  • capacitor-plugins — 从官方和社区来源安装并配置 Capacitor 插件。
  • capacitor-react — 用于 Capacitor 应用开发的 React 专属模式和最佳实践。
  • ionic-angular — 使用 Angular 的 Ionic Framework(基于 Capacitor 的 UI 组件、导航和主题)。
  • capacitor-app-upgrades — 将 Capacitor 应用升级到较新的主版本。

评论

支持 Markdown