I had also been facing the same issue but found a long hack.
The cause
Take a look at this line:
(<any>window).require
This line searches for the require()
method in the global window object, which is a NodeJS function and hence won't be injected during runtime if you're using Electron version > 5, because the webPreferences -> nodeIntegration
property of the BrowserWindow class was set to false by default.
The solution
There are two solutions:
- set
nodeIntegration: true
and contextIsolation: false
The app will work as expected BUT there is a security issue with this hack.
As quoted in this answer:
Electron apps are great because we get to use node, but this power is a double-edged sword. If we are not careful, we give someone access to node through our app, and with node a bad actor can corrupt your machine or delete your operating system files (among other things, I imagine).
- Using
contextBridge
and preload.js: This is derived from the above answer and other answers to this question.
I'll be presenting a workaround using Angular.
in the webPreferences
when creating the mainWindow
in main.js, make the following modifications:
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, "preload.js")
}
In the same directory where main.js
is there, create a preload.js
file and add the following code there (you may omit the receive()
declaration as for this use case it is not required).
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["openModal"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
PS: Reason for adding this code is already mentioned in the referenced answer so I won't be repeating that here.
Now, at runtime, electron will inject window.api.send()
and window.api.receive()
methods in the global window
object. If you try to access them directly in your angular component, linter will give error because they are not defined on the standard window object. To solve this, we will create an Angular service and inject in our application. This service will reference to the window object of the browser created at runtime, with the above two functions added.
Create a WindowRefService
and add the following code:
window-ref-service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WindowRefService {
getWindow(): any {
return window;
}
constructor() { }
get nativeWindow(): Window{
return this.getWindow();
}
}
Don't forget to register the service in the AppModule:
...
import { WindowRefService } from './window-ref.service';
...
@NgModule{
...
providers: [WindowRefService]
}
After that inject it into your component:
import { WindowRefService } from './window-ref.service';
...
private _window:any;
constructor(windowRef: WindowRefService){
this._window = windowRef.nativeWindow;
}
...
//openModal function
openModal(){
console.log("Open a modal");
this._window.api.send("openModal", /* data to be sent, if any*/);
}
Remove the private ipc: IpcRenderer | any;
and other existing code in the constructor as it is not required.