3
votes

I'm trying to figure out how I can close an electron app with an angular component. I removed the menu bar by setting frame: false on BrowserWindow({... inside main.js. I have a close button on the top right corner of the electron app from a component. I want to be able to close the app from the component.ts file on click, but I haven't seen any examples of closing electron from an angular component.

I thought the following would work, but didn't. I'm exporting a function from main.js and calling the function from the component. like so (see closeApp()):

const { app, BrowserWindow } = require('electron')
const url = require("url");
const path = require("path");

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      nodeIntegration: true
    }
  })
  mainWindow.maximize();
  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, `/dist/index.html`),
      protocol: "file:",
      slashes: true
    })
  );

...

function closeApp() {
  mainWindow = null
}

export { closeApp };

Then I would try and import it into component like

import { Component, OnInit } from '@angular/core';
import { closeApp } from '../../../main.js';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {

  }
  testClose() {
    closeApp();
  }

}

How can I close the electron app from angular? I appreciate any help!

2
I get build errors with electron ... Can you share those errors? - Roddy of the Frozen Peas
Correction: I did have build errors, but they were unrelated. The build errors are gone now so that's not the issue. - user6680
I think the answers from this question still apply: stackoverflow.com/questions/43314039/… - Roddy of the Frozen Peas
If I try this solution: stackoverflow.com/a/43314199/4350389 then I get compiler errors. Here are my updated changes and compiler errors from that solution. pastebin.com/yXLqaKXz Do you know how to resolve these errors? - user6680
Can't you use IPC calls? I usually have an Angular service that does all of the communication with the Node process. You could look at this article for more explanation - RJM

2 Answers

0
votes

Add this code in your main.ts file

mainWindow.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.
    
    mainWindow = null;
    
  });

return mainWindow;

mainWindow.on("Closed") will capture the click event on cross icon and that time we will return the win null which will close the window.

My main.ts file and it's function.

import { app, BrowserWindow, screen, Menu } from 'electron';
import * as path from 'path';
import * as url from 'url';
import * as electronLocalshortcut from 'electron-localshortcut'

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

function createWindow(): BrowserWindow {

  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,
    icon: path.join(__dirname, 'dist/assets/logo.png'),
    webPreferences: {
      nodeIntegration: true,
      allowRunningInsecureContent: (serve) ? true : false,
    },
  });


  // Disable refresh
  win.on('focus', (event) => {
    console.log("event of on fucous ");
    electronLocalshortcut.register(win, ['CommandOrControl+R', 'CommandOrControl+Shift+R', 'F5'], () => { })
  })

  win.on('blur', (event) => {
    console.log("event of on blue ");
    electronLocalshortcut.unregisterAll(win)
  })
  if (serve) {
    require('electron-reload')(__dirname, {
      electron: require(`${__dirname}/node_modules/electron`)
    });
    win.loadURL('http://localhost:4200');
  } else {
    win.loadURL(url.format({
      pathname: path.join(__dirname, 'dist/index.html'),
      protocol: 'file:',
      slashes: true
    }));
  }

  if (serve) {
    win.webContents.openDevTools();
  }

  win.on('close', (e) => {
    // Do your control here

    e.preventDefault();

  });
  // 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.
    // if (JSON.parse(localStorage.getItem('isRunning'))) {
    //   alert('Your timer is running')
    // } else {
    win = null;
    // }
  });


  return win;
}

try {

  // Custom menu.
  const isMac = process.platform === 'darwin'

  const template: any = [
    // { role: 'fileMenu' }
    {
      label: 'File',
      submenu: [
        isMac ? { role: 'close' } : { role: 'quit' }
      ]
    },
    // { role: 'editMenu' }
    {
      label: 'Window',
      submenu: [
        { role: 'minimize' },
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)

  // 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;
}

Thank you.

0
votes

You can use code like this from your browser window. Keep in mind it requires enableRemoteModule which is not recommended for security reasons. You can work around that with more complex code, but this should show you the basic idea

The easier but less secure way

In your main process:

mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    fullscreen: false,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })

From your mainWindow, in the renderer

Upon clicking the close button for your app (your custom button) you should have it run the following logic:

const win = remote.getCurrentWindow();
win.close();

https://www.electronjs.org/docs/api/browser-window#winclose

win.destroy() is also available, but win.close() should be preferred.


The harder but more secure way

This requires using a preload script That's another question, but I'll include the code for doing it this way, but it is harder to get working for a number of reasons.

That said, this is the way you should aim to implement things in the long run.

  import { browserWindow, ipcMain } from 'electron';

  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    fullscreen: false,
    preload: [Absolute file path to your preload file],
    webPreferences: {}
  });

  ipcMain.handle('close-main-window', async (evt) => {
    mainWindow.close();
    // You can also return values from here

  });


preload.js

  const { ipcRenderer } = require('electron');

  // We only pass closeWindow to be used in the renderer page, rather than passing the entire ipcRenderer, which is a security issue
  const closeWindow = async () => {
     await ipcRenderer.invoke('close-main-window'); // We don't actually need async/await here, but if you were returning a value from `ipcRenderer.invoke` then you would need async/await
  }

  window.api = {
    closeWindow
  }

renderer page

  
// I don't know Angular, but just call window.api.closeWindow() in response to the button you want to use