angular
Electron
Zone.JS
Angular4

Angular on Electron

初めまして、私が@jialipassionです、初めてQiitaで投稿して、宜しくお願いします。
今までhttps://github.com/angular/zone.js でいろいろContributeしていますが、今日はElectronでAngularの開発でいくつヒントを共用します。

  • Angular on Electron
  • Electron Native API (NgZoneを利用する)
  • 新しいzone-patch-electronの使い方(自動Patch)

Angular on Electron

ElectronでAngularの開発は普通のElectronの開発とあまり差がありません。
https://github.com/maximegris/angular-electron をベースに直接やってもいいですが、自分で@angular/cliでやっても問題ありません。

1. 環境構築

npm install @angular/cli
ng new electron-angular
cd electron-angular
npm install --save-dev electron electron-reload

そして、Electronのエントリのmain.tsを用意します。

import { app, BrowserWindow, screen } from 'electron';
import * as path from 'path';

let win, serve;
const args = process.argv.slice(1);
serve = args.some(val => val === '--serve');

if (serve) {
  require('electron-reload')(__dirname, {
  });
}

function createWindow() {

  const electronScreen = screen;
  const size = electronScreen.getPrimaryDisplay().workAreaSize;

  // Create the browser window.
  win = new BrowserWindow({
    x: 0,
    y: 0,
    width: size.width,
    height: size.height
  });

  // and load the index.html of the app.
  win.loadURL('file://' + __dirname + '/index.html');

  // Open the DevTools.
  if (serve) {
    win.webContents.openDevTools();
  }

  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store window
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win = null;
  });
}

try {

  // This method will be called when Electron has finished
  // initialization and is ready to create browser windows.
  // Some APIs can only be used after this event occurs.
  app.on('ready', createWindow);

  // Quit when all windows are closed.
  app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
      app.quit();
    }
  });

  app.on('activate', () => {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (win === null) {
      createWindow();
    }
  });

} catch (e) {
  // Catch Error
  // throw e;
}

これが普通のElectronのエントリとあまり変わりません。

2. zone-mix適用

Angularでzone.jsを利用して、ChangeDetectionを探知しています。@angular/cliでのsrc/polyfill.tsで下記のコードがあります。

import 'zone.js/dist/zone';  // Included with Angular CLI.

これがBrowser用のzone.jsをロードしました。でもElectronでBrowserとNodeJs両方のAPIがあって、Browserだけであれば、NodeJSのfsとかEventEmitterなどでzoneに対応されていません。たとえば、AngularのComponentで

fs.readFile(..., (err, data) => {
   // not in ngZone
   // will not auto update DOM
   this.content = data;
})

のようなコードで、自動てきにAngularのChangeDetectionを実行しません。
なので、Electronの場合、上記のBrowser zoneをBrowser+NodeJSのzoneを切り替える必要があります。
src/polyfill.tsで下記のように切り替えます。

//import 'zone.js/dist/zone';  // Included with Angular CLI.
import 'zone.js/dist/zone-mix';  // Included with Angular CLI.

Electron Native API

上記の対応で、NodeJSの基本のAPIが対応されました、でもElectronのAPIがまだ対応されていません。
たとえば、ElectronのMenuItemとか、DesktopCapturerとか、これらの非同期処理するとき、CallbackがNgZoneではないので、DOMが更新していません。たとえば、AngularのComponentで下記のMenuを初期化したら、

app.component.ts

const template = [{
  label: 'Edit',
  submenu: [
    {
      label: 'submenu',
      click: () => {
        // not in ngZone
    // title will not be updated in DOM
        this.title = 'menuclicked';
      }
    },
  ]
}];
const menu = this.electron.remote.Menu.buildFromTemplate(template);
this.electron.remote.Menu.setApplicationMenu(menu);

TitleがDOMに更新されていません。

このとき、ngZone.runが必要です。

app.component.ts

constructor(private ngZone: NgZone) {...}

ngOnInit() {
  const template = [{
    label: 'Edit', 
    submenu: [
      {
        label: 'submenu',
        click: () => {
          this.ngZone.run(() => {
             this.title = 'menuclicked';
          });
        }
      },
    ]
  }];
  const menu = this.electron.remote.Menu.buildFromTemplate(template);
  this.electron.remote.Menu.setApplicationMenu(menu);
}

新しいzone-patch-electron

上記のNgZoneでElectron Native APIを対応できますが、NgZoneを意識してちょっと面倒かもしれませんが、
zone.jsで今新しいzone-patch-electronを提供しました、(まだリリースしていないですが。。。)
https://github.com/angular/zone.js/pull/983

これがリリースしたら、ngZone.runがいらなくなります。
使い方が、src/polyfill.ts

import 'zone.js/dist/zone';  // Included with Angular CLI.
import 'zone.js/dist/zone-patch-electron';  // Add this line.

を追加したら、自動的に大部のElectron APIを自動てきにPatchされます。ちょっとまだリリースしていないですが、
次のバージョンをおまちください。

以上!

どうもありがとうございました!