Loading...
Loading...
Compare original and translation side by side
npm install -g @angular/clinpm install -g @angular/cliangular.jsonpackage.jsoncapacitor.config.tscapacitor.config.jsonangular.jsonpackage.jsoncapacitor.config.tscapacitor.config.json@angular/corepackage.json@capacitor/corepackage.jsonsrc/main.tsbootstrapApplicationplatformBrowserDynamic().bootstrapModuleangular.jsonandroid/ios/capacitor.config.tscapacitor.config.jsonoutputPathangular.jsonprojects > <project-name> > architect > build > options > outputPathwebDirpackage.json@angular/corepackage.json@capacitor/coresrc/main.tsbootstrapApplicationplatformBrowserDynamic().bootstrapModuleangular.jsonandroid/ios/capacitor.config.tscapacitor.config.jsonangular.jsonprojects > <项目名称> > architect > build > options > outputPathoutputPathwebDir@capacitor/corepackage.jsonnpm install @capacitor/core
npm install -D @capacitor/clinpx cap initdist/<project-name>/browserdist/<project-name>webDircapacitor.config.tscapacitor.config.jsoncapacitor.config.tsimport 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"
}ng build
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios
npx cap syncpackage.json@capacitor/corenpm install @capacitor/core
npm install -D @capacitor/clinpx cap initdist/<项目名称>/browserdist/<项目名称>capacitor.config.tscapacitor.config.jsonwebDircapacitor.config.tsimport 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"
}ng build
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios
npx cap syncmy-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.jsonandroid/ios/src/src/app/my-app/
├── android/ # Android原生项目目录
├── ios/ # iOS原生项目目录
├── src/
│ ├── app/
│ │ ├── app.component.ts
│ │ ├── app.config.ts # 独立组件模式:应用配置文件
│ │ ├── app.module.ts # NgModule模式:根模块文件
│ │ ├── app.routes.ts # 路由配置文件
│ │ └── services/ # 用于封装Capacitor插件的Angular服务目录
│ ├── environments/
│ │ ├── environment.ts
│ │ └── environment.prod.ts
│ ├── index.html
│ └── main.ts
├── angular.json
├── capacitor.config.ts # 或capacitor.config.json
├── package.json
└── tsconfig.jsonandroid/ios/src/src/app/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;
}
}import { Component } from '@angular/core';
import { Geolocation } from '@capacitor/geolocation';
@Component({
selector: 'app-location',
template: `
<div>
<p>纬度: {{ latitude }}</p>
<p>经度: {{ longitude }}</p>
<button (click)="getCurrentPosition()">获取位置信息</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;
}
}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;
}
}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()">拍照</button>
<img *ngIf="photoUrl" [src]="photoUrl" alt="拍摄的照片" />
`,
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;
}
}NgZoneNgZone.run()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();
}
}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();
}
}NgZone.run()NgZoneNgZone.run()import { Component, OnInit, OnDestroy } from '@angular/core';
import { Network, ConnectionStatus } from '@capacitor/network';
import { PluginListenerHandle } from '@capacitor/core';
@Component({
selector: 'app-network',
template: `<p>网络状态: {{ networkStatus }}</p>`,
standalone: true,
})
export class NetworkComponent implements OnInit, OnDestroy {
networkStatus = '未知';
private listenerHandle: PluginListenerHandle | null = null;
async ngOnInit() {
// 问题:此回调函数运行在NgZone外部,模板不会更新
this.listenerHandle = await Network.addListener('networkStatusChange', (status) => {
this.networkStatus = status.connected ? '在线' : '离线';
});
}
async ngOnDestroy() {
await this.listenerHandle?.remove();
}
}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>网络状态: {{ networkStatus }}</p>`,
standalone: true,
})
export class NetworkComponent implements OnInit, OnDestroy {
private ngZone = inject(NgZone);
networkStatus = '未知';
private listenerHandle: PluginListenerHandle | null = null;
async ngOnInit() {
this.listenerHandle = await Network.addListener('networkStatusChange', (status) => {
this.ngZone.run(() => {
this.networkStatus = status.connected ? '在线' : '离线';
});
});
}
async ngOnDestroy() {
await this.listenerHandle?.remove();
}
}NgZone.run(() => { ... })ngOnInitngOnDestroyngOnInitngOnDestroyimport { 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_INITIALIZERAppComponentapp.config.tsimport { 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,
},
],
};app.component.tsimport { 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) {}
}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_INITIALIZERAppComponentapp.config.tsimport { 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,
},
],
};app.component.tsimport { 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) {}
}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
}
}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()">打开设备设置</button>
}
`,
standalone: true,
})
export class SettingsComponent {
platformService = inject(PlatformService);
openNativeSettings() {
// 仅原生平台执行的逻辑
}
}App.addListener('appUrlOpen', ...)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);
}
});
});
}
}DeepLinkServiceAPP_INITIALIZERApp.addListener('appUrlOpen', ...)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;
// 导航到与深度链接路径匹配的路由
// 根据应用的URL协议调整路径解析逻辑
if (path) {
this.router.navigateByUrl(path);
}
});
});
}
}DeepLinkServiceAPP_INITIALIZERApp.addListener('backButton', ...)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();
}
});
});
}
}App.addListener('backButton', ...)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();
}
});
});
}
}ng build
npx cap syncnpx cap run android
npx cap run iosnpx cap open android
npx cap open iosnpx cap run android --livereload --external
npx cap run ios --livereload --externalng serveng build
npx cap syncnpx cap run android
npx cap run iosnpx cap open android
npx cap open iosnpx cap run android --livereload --external
npx cap run ios --livereload --externalng serveNgZone.run(() => { ... })webDirnpx cap syncwebDircapacitor.config.tscapacitor.config.jsondist/<project-name>/browserdist/<project-name>npx cap syncpackage.jsonngOnDestroyPluginListenerHandleaddListenerhandle.remove()android/app/src/main/AndroidManifest.xmlios/App/App/Info.plistDeepLinkServicecanGoBackApp.exitApp()ng buildoutputPathangular.jsondist/<project-name>/browserNgZone.run(() => { ... })webDirnpx cap synccapacitor.config.tscapacitor.config.jsonwebDirdist/<项目名称>/browserdist/<项目名称>npx cap syncpackage.jsonngOnDestroyaddListenerPluginListenerHandlehandle.remove()android/app/src/main/AndroidManifest.xmlios/App/App/Info.plistDeepLinkServiceApp.exitApp()canGoBackng buildangular.jsonoutputPathdist/<项目名称>/browsercapacitor-app-creationcapacitor-app-developmentcapacitor-pluginscapacitor-reactionic-angularcapacitor-app-upgradescapacitor-app-creationcapacitor-app-developmentcapacitor-pluginscapacitor-reactionic-angularcapacitor-app-upgrades