I am having a strange issue with an Angular 7.1.1 and Electron 4.1.4 project.
Data Flow:
Problem: When the data gets to my write-docx function it is missing a sub array of objects that are essential to the export process.
I have verified that the data is perfect in the Chrome Developer Tools console of electron at the moment just before it sends the data to the docx-templater.service and just before that service sends it to the ipcRenderer (meaning my data service and Report Builder functions are working as designed). When I check the data in the main.ts by saving the data off to a JSON file it is missing the controls sub array within the second object of the JSON only. The controls sub array shows up in the first object as expected.
I will note that what is coming out of the ipcMain function is a properly formed JSON file so it has really just excluded the "controls" sub array and is not truncating due to memory or buffer limits or anything like that.
report-builder.component.ts
createReport() {
if (this.reportBuilderFG.get('allControls').value) {
this.db.fnGetCompleteControlList()
.then((groups: Group[]) => {
this.word.createCompleteDocument(groups, this.reportBuilderFG.get('folder').value + '\\filename.docx')
.then(() => {
this.openSnackBar(this.reportBuilderFG.get('folder').value + '\\filename.docx created successfully');
});
});
} else {
// Do other stuff
}
docx-templater.service.ts
createCompleteDocument(data, folder: string): Promise<boolean> {
return new Promise(resolve => {
console.log(data) <=== Data is perfect here.
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
resolve();
});
}
main.ts
import { writeCompleteDocument } from './node_scripts/write-docx';
ipcMain.on('writeCompleteDocument', (event, arg) => {
fs.writeFileSync("IPCdata.json", arg.data); // <==== Part of the data is missing here.
writeCompleteDocument(arg.data, arg.folder);
});
Good Data Example (some keys and objects excluded for brevity)
[
{
"name": "General Security",
"order": 1,
"subgroups": [
{
"_id": "GOV",
"name": "Governance",
"order": 1,
"controls": [
{
"group": "GS",
"subgroup": "GOV",
"active": true,
"printOrder": 1,
"name": "This is my GS control name",
"requirements": [
{
"id": "SA01",
"active": true,
"order": 1,
"type": "SA",
"applicability": [
"ABC",
"DEF",
"GHI"
],
},
{ ... 3 more }
],
"_id": "GSRA-03",
"_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
},
{ ... 3 more }
],
"_id": "GS",
"_rev": "1-b94d1651589eefd5ef0a52360dac6f9d"
},
{
"order": 2,
"name": "IT Security",
"subgroups": [
{
"_id": "PLCY",
"order": 1,
"name": "Policies",
"controls": [ <==== This entire sub array is missing when exporting from IPC Main
{
"group": "IT",
"subgroup": "PLCY",
"active": true,
"printOrder": 1,
"name": "This is my IT control name",
"requirements": [
{
"id": "SA01",
"active": true,
"order": 1,
"type": "SA",
"applicability": [
"ABC",
"DEF",
"GHI"
],
}
],
"_id": "GSRA-03",
"_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
}
}
],
"_id": "IT",
"_rev": "2-e6ff53456e85b45d9bafd791652a945c"
}
]
I would have expected the ipcRenderer to pass a JSON exactly as it is to the ipcMain.on function, but somehow it is trimming part of the data. I have even tried strigifying the data before sending it to the renderer and then parsing it on the other side but that did nothing.
Could this be an async thing? I am at a loss of where to go next to debug and find what idiot mistake I made in the process.
Also, I realize that the above data flow seems overly complex for what I am doing, and that I can probably do it easier, but it makes sense (kinda) for the way the whole application is structured so I am going to go with it if I can squash this bug.
Looks like your createCompleteDocument()
function is set up incorrectly. A quick search showed me that ipcRenderer
is an async function, but you are responding to it (almost) synchronously.
You have the following, which is (probably) incorrect (actually it's definitely incorrect, because you've typed typed the return as Promise<boolean>
when it is Promise<void>
):
createCompleteDocument(data, folder: string): Promise<boolean> {
return new Promise(resolve => {
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
resolve();
});
}
ipcRenderer#send()
is async but you are calling resolve()
immediately afterwards without waiting for the function to resolve. This probably explains why adding the setTimeout()
is fixing the problem for you. Looking at the ipcRenderer
docs, the following probably does what you want:
createCompleteDocument(data, folder: string): Promise<Event> {
return new Promise(resolve => {
ipcRenderer.once('writeCompleteDocument', resolve);
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
});
}
Looks like the callback is passed an Event object.
Another option would be to simply replace ipcRenderer#send()
with ipcRenderer#sendSync()
in your original code, but as pointed out in that method's documentation :
Sending a synchronous message will block the whole renderer process, unless you know what you are doing you should never use it.
Making use of ipcRenderer#send()
and ipcRenderer#once()
is almost definitely the way to go.
Seperately, you can clean up the code by switching to async/await functions. For example:
async createReport(): Promise<void> {
if (this.reportBuilderFG.get('allControls').value) {
const groups: Group[] = await this.db.fnGetCompleteControlList();
await this.word.createCompleteDocument(
groups,
this.reportBuilderFG.get('folder').value + '\\filename.docx'
);
// Unclear if this function is actually async
await this.openSnackBar(
this.reportBuilderFG.get('folder').value +
'\\filename.docx created successfully'
);
} else {
// Do other stuff
}
}