Angular Capacitor Integration
Introduction
Capacitor is a modern native runtime that makes it easy to build web apps that run natively on iOS, Android, and the web. In this tutorial, we'll explore how to integrate Capacitor with Angular applications to build cross-platform mobile apps using your existing web development skills.
Capacitor provides a compatibility layer that allows your Angular application to access native device features (camera, geolocation, storage, etc.) using a consistent API across platforms. This integration enables you to leverage your Angular expertise to create mobile applications without learning platform-specific languages like Swift or Kotlin.
Prerequisites
Before we get started, make sure you have:
- Node.js (version 14.x or higher)
- npm or yarn package manager
- Angular CLI installed globally
- Basic knowledge of Angular
- Android Studio (for Android development)
- Xcode (for iOS development, macOS only)
Setting Up a New Angular Project with Capacitor
Let's start by creating a new Angular project and integrating Capacitor from scratch.
Step 1: Create a New Angular Project
# Create a new Angular project
ng new angular-capacitor-demo
cd angular-capacitor-demo
# Build the project
ng build
Step 2: Add Capacitor to Your Project
# Install Capacitor Core and CLI
npm install @capacitor/core @capacitor/cli
# Initialize Capacitor in your project
npx cap init
When running the initialization command, you'll be prompted for:
- App name (e.g., "Angular Capacitor Demo")
- App ID (e.g., "com.example.angularcapacitor")
Step 3: Add Platforms
Now, let's add the platforms you want to target:
# Add Android platform
npm install @capacitor/android
npx cap add android
# Add iOS platform (macOS only)
npm install @capacitor/ios
npx cap add ios
Configuring Your Angular Application for Capacitor
For optimal results, we need to make some adjustments to our Angular configuration.
Update Angular Configuration
Modify your angular.json
file to output the build to the correct directory:
{
"projects": {
"angular-capacitor-demo": {
"architect": {
"build": {
"options": {
"outputPath": "dist/angular-capacitor-demo",
"baseHref": "./",
"sourceMap": true
}
}
}
}
}
}
Configure capacitor.config.ts
Create or update the capacitor.config.ts
file in your project root:
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.angularcapacitor',
appName: 'Angular Capacitor Demo',
webDir: 'dist/angular-capacitor-demo',
bundledWebRuntime: false,
server: {
androidScheme: 'https'
}
};
export default config;
Working with Native Device Features
One of the main advantages of Capacitor is access to native device features. Let's implement a simple example that uses the Camera API.
Step 1: Install the Camera Plugin
npm install @capacitor/camera
npx cap sync
Step 2: Create a Camera Service
Create a new service to handle camera operations:
ng generate service services/camera
Update the camera.service.ts
file:
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
@Injectable({
providedIn: 'root'
})
export class CameraService {
constructor() { }
async takePicture(): Promise<string> {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
// Return the webPath for display in our app
return image.webPath || '';
} catch (error) {
console.error('Error taking photo', error);
return '';
}
}
}
Step 3: Create a Component to Use the Camera
Let's create a component to use our camera service:
ng generate component components/photo-taker
Update the photo-taker.component.ts
file:
import { Component } from '@angular/core';
import { CameraService } from '../../services/camera.service';
@Component({
selector: 'app-photo-taker',
template: `
<div class="container">
<h2>Take a Picture</h2>
<button (click)="capturePhoto()" class="capture-btn">
Capture Photo
</button>
<div *ngIf="photoUrl" class="photo-container">
<img [src]="photoUrl" alt="Captured photo" class="captured-img">
</div>
</div>
`,
styles: [`
.container {
text-align: center;
padding: 20px;
}
.capture-btn {
padding: 10px 20px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
margin-bottom: 20px;
}
.photo-container {
margin-top: 20px;
}
.captured-img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
}
`]
})
export class PhotoTakerComponent {
photoUrl: string = '';
constructor(private cameraService: CameraService) { }
async capturePhoto() {
this.photoUrl = await this.cameraService.takePicture();
}
}
Step 4: Add the Component to Your App
Update your app.component.html
to include the new component:
<div class="content">
<h1>Angular Capacitor Demo</h1>
<p>Access native features from your Angular app</p>
<app-photo-taker></app-photo-taker>
</div>
Android Platform-Specific Configuration
For Android, we need to add permissions to the Android Manifest file.
Update Android Manifest
Open android/app/src/main/AndroidManifest.xml
and add the camera permissions:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application>
<!-- Your application config -->
</application>
</manifest>
iOS Platform-Specific Configuration
For iOS, we need to update the Info.plist file to request camera permissions.
Update Info.plist
Open ios/App/App/Info.plist
and add:
<dict>
<!-- Other configuration -->
<key>NSCameraUsageDescription</key>
<string>We need access to your camera to take photos.</string>
<!-- Other configuration -->
</dict>
Testing and Running Your App
Building Your Angular App
Whenever you make changes to your Angular code, you need to rebuild and sync with Capacitor:
# Build your Angular app
ng build
# Sync with Capacitor
npx cap sync
Running on Android
# Open in Android Studio
npx cap open android
Then use Android Studio to run the app on an emulator or physical device.
Running on iOS (macOS only)
# Open in Xcode
npx cap open ios
Then use Xcode to run the app on a simulator or physical device.
Creating a Production Build
When you're ready to create a production build:
# Build Angular app in production mode
ng build --configuration production
# Sync with Capacitor
npx cap sync
# Open in Android Studio or Xcode to create the final app bundle/APK
npx cap open android
npx cap open ios
Real-World Application: Building a Location Tracker
Let's create a practical example that uses Capacitor's Geolocation plugin to track a user's location:
Step 1: Install the Geolocation Plugin
npm install @capacitor/geolocation
npx cap sync
Step 2: Create a Location Service
ng generate service services/location
Update the location.service.ts
file:
import { Injectable } from '@angular/core';
import { Geolocation, Position } from '@capacitor/geolocation';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LocationService {
private locationSubject = new BehaviorSubject<Position | null>(null);
public location$ = this.locationSubject.asObservable();
private watchId: string | null = null;
constructor() { }
async getCurrentPosition(): Promise<Position> {
try {
const position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true
});
this.locationSubject.next(position);
return position;
} catch (error) {
console.error('Error getting current position', error);
throw error;
}
}
startWatchingPosition() {
if (this.watchId) {
return;
}
Geolocation.watchPosition(
{ enableHighAccuracy: true },
(position, err) => {
if (err) {
console.error('Error watching position', err);
return;
}
if (position) {
this.locationSubject.next(position);
}
}
).then(id => {
this.watchId = id;
});
}
stopWatchingPosition() {
if (this.watchId) {
Geolocation.clearWatch({ id: this.watchId });
this.watchId = null;
}
}
}
Step 3: Create a Location Tracker Component
ng generate component components/location-tracker
Update the location-tracker.component.ts
file:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { LocationService } from '../../services/location.service';
import { Subscription } from 'rxjs';
import { Position } from '@capacitor/geolocation';
@Component({
selector: 'app-location-tracker',
template: `
<div class="location-container">
<h2>Location Tracker</h2>
<div class="controls">
<button (click)="getLocation()" class="location-btn">
Get Current Location
</button>
<button (click)="toggleTracking()" class="tracking-btn"
[ngClass]="{'tracking-active': isTracking}">
{{ isTracking ? 'Stop Tracking' : 'Start Tracking' }}
</button>
</div>
<div *ngIf="currentPosition" class="position-info">
<h3>Current Position:</h3>
<p><strong>Latitude:</strong> {{ currentPosition.coords.latitude.toFixed(6) }}</p>
<p><strong>Longitude:</strong> {{ currentPosition.coords.longitude.toFixed(6) }}</p>
<p><strong>Accuracy:</strong> {{ currentPosition.coords.accuracy.toFixed(2) }} meters</p>
<p><strong>Timestamp:</strong> {{ formatTimestamp(currentPosition.timestamp) }}</p>
</div>
<div *ngIf="errorMessage" class="error">
{{ errorMessage }}
</div>
</div>
`,
styles: [`
.location-container {
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
max-width: 500px;
margin: 0 auto;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.location-btn, .tracking-btn {
padding: 10px 15px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
flex: 1;
}
.location-btn {
background-color: #4285f4;
color: white;
}
.tracking-btn {
background-color: #34a853;
color: white;
}
.tracking-active {
background-color: #ea4335;
}
.position-info {
background-color: white;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
.error {
color: #ea4335;
margin-top: 15px;
padding: 10px;
background-color: rgba(234, 67, 53, 0.1);
border-radius: 4px;
}
`]
})
export class LocationTrackerComponent implements OnInit, OnDestroy {
currentPosition: Position | null = null;
isTracking = false;
errorMessage = '';
private locationSubscription: Subscription | null = null;
constructor(private locationService: LocationService) { }
ngOnInit() {
this.locationSubscription = this.locationService.location$.subscribe(
position => {
if (position) {
this.currentPosition = position;
this.errorMessage = '';
}
}
);
}
ngOnDestroy() {
this.locationService.stopWatchingPosition();
if (this.locationSubscription) {
this.locationSubscription.unsubscribe();
}
}
async getLocation() {
try {
this.currentPosition = await this.locationService.getCurrentPosition();
this.errorMessage = '';
} catch (error: any) {
this.errorMessage = `Error getting location: ${error.message}`;
}
}
toggleTracking() {
if (this.isTracking) {
this.locationService.stopWatchingPosition();
} else {
try {
this.locationService.startWatchingPosition();
} catch (error: any) {
this.errorMessage = `Error starting location tracking: ${error.message}`;
}
}
this.isTracking = !this.isTracking;
}
formatTimestamp(timestamp: number): string {
return new Date(timestamp).toLocaleTimeString();
}
}
Step 4: Update Platform Permissions
For Android, add location permissions to the AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
For iOS, update Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show it on the map.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need your location to track your position even when the app is in the background.</string>
Step 5: Add the Location Tracker to Your App
Update your app.component.html
:
<div class="content">
<h1>Angular Capacitor Demo</h1>
<p>Access native features from your Angular app</p>
<app-photo-taker></app-photo-taker>
<div class="divider"></div>
<app-location-tracker></app-location-tracker>
</div>
Add some CSS to app.component.css
:
.divider {
height: 2px;
background-color: #e0e0e0;
margin: 20px 0;
}
Best Practices for Angular Capacitor Integration
-
Update Regularly: Keep Capacitor and its plugins updated to benefit from the latest features and security fixes.
-
Optimize for Mobile:
- Use responsive design principles
- Implement touch-friendly UI components
- Ensure fast loading times
-
Handle Platform Differences: Use Capacitor's platform detection to handle platform-specific logic:
typescriptimport { Capacitor } from '@capacitor/core';
if (Capacitor.getPlatform() === 'ios') {
// iOS-specific code
} else if (Capacitor.getPlatform() === 'android') {
// Android-specific code
} else {
// Web-specific code
} -
Test on Real Devices: Emulators/simulators can't fully replicate the experience on real devices, especially for features like camera, geolocation, etc.
-
Use Live Reload for Development: Enable live reload to speed up your development process:
bashnpx cap run android -l --external
npx cap run ios -l --external -
Bundle Size Management: Keep your Angular bundle size small to ensure fast startup times on mobile devices.
Common Issues and Troubleshooting
Issue: App Not Syncing with Capacitor
Solution: Make sure you run ng build
before npx cap sync
to ensure the latest Angular build is copied to the native projects.
Issue: Plugin Not Found
Solution: After installing a plugin, always run npx cap sync
to update the native projects.
Issue: Android Build Errors
Solution: Check your Android Studio setup, Gradle version compatibility, and make sure your path doesn't contain special characters.
Issue: iOS Build Errors
Solution: Verify your Xcode installation, CocoaPods setup, and ensure you're running on macOS for iOS development.
Summary
In this tutorial, we've learned how to integrate Capacitor with Angular to build cross-platform mobile applications. We covered:
- Setting up a new Angular project with Capacitor
- Configuring Angular and Capacitor for optimal integration
- Accessing native device features like the camera
- Platform-specific configurations for Android and iOS
- Creating a real-world location tracker application
- Best practices and troubleshooting common issues
Capacitor provides a powerful bridge between your Angular web application and native mobile platforms, allowing you to build feature-rich mobile apps using your existing web development skills. By following this guide, you now have the foundation to build professional-grade mobile applications with Angular and Capacitor.
Additional Resources
- Official Capacitor Documentation
- Angular Documentation
- Ionic Framework (works well with Capacitor)
- Capacitor Community Plugins
Exercises
- Basic: Add the Capacitor Storage plugin to store and retrieve the last photo taken in the photo-taker component.
- Intermediate: Implement a dark mode toggle that uses Capacitor's Preferences API to remember the user's choice.
- Advanced: Create a note-taking app that uses Capacitor plugins for camera, geolocation, and local storage.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)