如何使用Electron + Angular 构建跨平台桌面应用
随着现代前端技术的发展,开发跨平台桌面应用已经不再依赖传统的 C++ 或 Java 桌面框架。Electron 提供了一个基于 Chromium 和 Node.js 的运行时环境,使得使用 Web 技术构建桌面应用成为可能。而 Angular 作为成熟的前端框架,则提供了强大的组件化和模块化能力。

一、为什么选择 Electron + Angular
- Electron 提供原生的桌面能力(系统托盘、原生菜单、快捷键、文件系统、自动更新等)
- Angular 提供强大的前端工程化能力(TypeScript、依赖注入、RxJS、模块化、AOT 编译、强大的 CLI)
- 两者结合既能保持 Web 开发的高效,又能获得接近原生的桌面体验
- 企业级项目中代码可维护性、类型安全、测试能力远超纯 Electron + HTML/JS 方案
二、项目初始化
1. 创建 Angular 项目
# 推荐使用 Angular 18+
ng new electron-angular-todo --routing=true --style=scss --strict
cd electron-angular-todo2. 添加 Electron 支持
# 安装 electron
npm install --save-dev electron @types/electron
# 安装常用工具包
npm install --save-dev wait-on concurrently electron-builder3. 配置 package.json 主入口
{
"name": "electron-angular-todo",
"main": "main.js",
"scripts": {
"ng": "ng",
"start": "ng serve",
"electron": "ng build --base-href ./ && electron .",
"electron:dev": "concurrently \"ng build --watch\" \"wait-on http://localhost:4200 && electron .\"",
"build:win": "ng build --configuration production && electron-builder --win",
"build:mac": "ng build --configuration production && electron-builder --mac",
"build:linux": "ng build --configuration production && electron-builder --linux"
}
}4. 创建 Electron 主进程文件 main.js
// main.js
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, globalShortcut } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
let mainWindow;
let tray = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
show: false, // 先不显示,内容加载完再 show 避免白屏
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
const startUrl = isDev
? 'http://localhost:4200'
: `file://${path.join(__dirname, '../dist/electron-angular-todo/browser/index.html')}`;
mainWindow.loadURL(startUrl);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
if (isDev) mainWindow.webContents.openDevTools();
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.whenReady().then(() => {
createWindow();
createTray();
// 注册全局快捷键 Cmd/Ctrl + Shift + T 显示/隐藏窗口
globalShortcut.register('CommandOrControl+Shift+T', () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
// 创建系统托盘
function createTray() {
const iconPath = path.join(__dirname, 'assets/tray-icon.png');
const icon = nativeImage.createFromPath(iconPath);
tray = new Tray(icon.resize({ width: 16, height: 16 }));
const contextMenu = Menu.buildFromTemplate([
{ label: '显示主窗口', click: () => mainWindow.show() },
{ label: '退出应用', click: () => app.quit() }
]);
tray.setToolTip('Angular Todo Desktop');
tray.setContextMenu(contextMenu);
tray.on('click', () => mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show());
}5. 创建 Preload 脚本(安全上下文桥)
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// 主进程 → 渲染进程(单向)
onUpdateAvailable: (callback) => ipcRenderer.on('update-available', callback),
onUpdateDownloaded: (callback) => ipcRenderer.on('update-downloaded', callback),
// 渲染进程 → 主进程(双向)
minimize: () => ipcRenderer.send('window-minimize'),
maximize: () => ipcRenderer.send('window-maximize'),
close: () => ipcRenderer.send('window-close'),
// 托盘消息
showTrayMessage: (title, body) => ipcRenderer.send('show-tray-message', title, body)
});三、在 Angular 中使用 Electron 能力
1. 定义类型声明
// src/types/electron.d.ts
interface ElectronAPI {
onUpdateAvailable: (callback: () => void) => void;
onUpdateDownloaded: (callback: () => void) => void;
minimize: () => void;
maximize: () => void;
close: () => void;
showTrayMessage: (title: string, body: string) => void;
}
declare global {
interface Window {
electronAPI: ElectronAPI;
}
}2. 创建窗口控制服务
// src/app/services/window-control.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class WindowControlService {
minimize() {
if (this.isElectron()) {
window.electronAPI.minimize();
}
}
maximize() {
if (this.isElectron()) {
window.electronAPI.maximize();
}
}
close() {
if (this.isElectron()) {
window.electronAPI.close();
} else {
window.close();
}
}
isElectron(): boolean {
return !!(window && window.electronAPI);
}
}3. 自动更新服务(使用 electron-updater)
安装依赖:
npm install electron-updater主进程添加更新逻辑:
// main.js 追加
const { autoUpdater } = require('electron-updater');
autoUpdater.setFeedURL({
provider: 'github',
owner: 'your-username',
repo: 'electron-angular-todo',
private: false
});
app.whenReady().then(() => {
// ... 之前的代码
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-available', () => {
mainWindow.webContents.send('update-available');
});
autoUpdater.on('update-downloaded', () => {
mainWindow.webContents.send('update-downloaded');
});
});
ipcMain.on('restart-app', () => {
autoUpdater.quitAndInstall();
});Angular 更新提示组件:
// update-notification.component.ts
@Component({
selector: 'app-update-notification',
template: `
<div class="update-banner" *ngIf="updateReady">
新版本已下载,<button (click)="restart()">立即重启更新</button>
</div>
`
})
export class UpdateNotificationComponent implements OnInit, OnDestroy {
updateReady = false;
ngOnInit() {
if (window.electronAPI) {
window.electronAPI.onUpdateDownloaded(() => this.updateReady = true);
}
}
restart() {
ipcRenderer.send('restart-app');
}
ngOnDestroy() {
// 清理监听
}
}四、实战案例:企业级待办事项应用
功能清单:
- 任务增删改查(本地 IndexedDB 存储,使用 @ngneat/elf)
- 系统托盘常驻 + 未完成任务数字角标
- 全局快捷键创建任务
- 自动更新
- 原生通知提醒
- 深色模式适配(Angular Material)
- 打包发布(Windows/macOS/Linux)
核心代码片段:
// 托盘未读角标更新
this.store.query(getUnfinishedCount).subscribe(count => {
if (window.electronAPI) {
const title = count > 0 ? `(${count}) Todo` : 'Todo';
window.electronAPI.showTrayMessage(title, count ? `${count} 个待办事项` : '全部完成');
}
});五、打包配置(electron-builder)
// package.json 中的 build 配置
"build": {
"appId": "com.example.todo",
"productName": "Angular Todo",
"directories": {
"output": "release/"
},
"files": [
"dist/electron-angular-todo/**/*",
"main.js",
"preload.js"
],
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.productivity"
},
"win": {
"target": ["nsis", "portable"],
"icon": "build/icon.ico"
},
"linux": {
"target": ["AppImage", "deb"]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
}执行打包:
npm run build:win
# 或
npm run build:mac六、最佳实践总结
- 始终开启 contextIsolation + preload 脚本,确保安全
- 使用 Angular 的 AOT + production 构建,体积可控制在 15MB 以内(压缩后安装包 ~50MB)
- 自动更新推荐使用 GitHub Releases + electron-updater(最简单)
- 大文件操作、数据库推荐使用主进程 + IPC,避免阻塞渲染线程
- 开发时使用 electron:dev 脚本实现热重载
- 托盘图标建议提供多尺寸(16/24/32/48/256)+ @2x 高清版本
通过以上完整方案,你可以快速构建一个技术栈现代、体验接近原生、维护性极高的跨平台桌面应用。Electron + Angular 是目前最成熟、最适合中大型桌面项目的组合之一。
THE END