Change Detection works intermittently when receiving data from Electron Container IPC Channel
Asked Answered
B

1

10

I have an application that is listening for incoming data from an IPC Renderer Channel. Here is my setup:

container that sends data to angular app (mainWindow):

mainWindow.loadURL('http://www.myangularapp.com') //where the angular app lives (example url).
mainWindow.webContents.on('did-finish-load', () => {
      const data = { name: "John Doe", address: "123 Main St", city: "NY"}
      mainWindow.webContents.send('data-from-container', data)
        }
    })

angular app:

constructor(
    private store: Store<fromStore.AppState>,
    private cd: ChangeDetectorRef,
    private zone: NgZone,
  ) {}

  ngOnInit() {
this.isLoading = true;
    if (this.electronService.ipcRenderer) {
      this.electronService.ipcRenderer.on('data-from-container', (event, data) => {
        this.zone.run(() => {
          if(data){
            setTimeout(() => {
              console.log(data) // verified data is always received
              this.formData = data; // property that form uses to populate data in the dom
              this.prepopulateForm(data) // method that places incoming data in ngrx store
            }, 1000)
          }
        })
      })
    }

    this.store.select('containerData').subscribe(data => {
        setTimeout(()=> {
          console.log(data) // data is always being set in ngrx
          this.isLoading = false
        }, 5000);
    })

  }

Everytime the IPC Channel emits the 'data-from-container' event, the data is always getting received from my OnInit call, but the data doesn't always get set in my form! The pattern that i've noticed is typically that the data does not prepopulate the form when the angular app first launches inside the container, after the initial launch, every subsequent time the app is launched, the data appears.

I've tried using ngZone, setTimeout, and detectChange methods to trigger Change Detection so the Angular App can detect the newly set formData, but its not consistently prepopulating the form. Any suggestions on how to fix this?

Belga answered 15/7, 2019 at 23:0 Comment(12)
Try: if (data) { setTimeout(() => { this.zone.run(() => { this.formData = data; ... }); }, 1000); }.Dorelle
still same result :/Belga
why do you use setTimeout inside zone.run ?Gielgud
trial and error, ive tried without the setTimeout as wellBelga
can you confirm the first event 'data-from-container' is triggered after the component is initialized? and what do you mean with 'the angular app first launches'? the 1st app load?Eugeniusz
Correct when the app first loads. Ive added additional code to show when data is sent from the containerBelga
could you try the web-contents event 'dom-ready' instead of 'did-finish-load' to launch the 1st data through 'data-from-container' event?Eugeniusz
Could you show all the code, which is relevant to your form and data binding?Whipstall
If you can create a git repo with minimal reproducible code, i am sure it would be easy to provide a solutionSchwab
Please provide more details in your question. For instance, you said you tried detectChanges but there seems to be no mention here that the ChangeDetectionStrategy you have used is OnPush. If you are using OnPush, you will need to trigger changeDetection manually or assign formData immutably. I'm not sure why you are using ngZone when you don't seem to be running anything outside Angular. It would be easier to help if you provided the entire component and template or even better, a StackBlitz example.Toneme
@Belga i think your view is not completely created so that the first element is logged but not shown ! For that you have to wait until the view is initialized without loosing your first emission. Somthing like combineLatest('data-from-container', viewInitialized)Sauls
@Sauls can you provide that as an answer so I can test this out?Belga
S
3

I have a very basic knowledge of electron, so i'll try to show you the idea. For me, your problem comes from the initialization of the view. You're not loosing events because you can see them in the console but not in the view, which enforces my guesses.

As shown in your code, your are sending only one event (I suppose it's only for testing raison) and we want to show it when the view is rendered.

In your component add a subject which informes us that the view is initialized, like:

import { Subject, combineLatest, fromEvent } from 'rxjs';

viewInitialized$ = new Subject();

...

ngAfterViewInit() {
 this.viewInitialized$.next();
}

...

Now we can wait the two emissions to come, one from the ipcRenderer and the other from viewInitialized$by using combineLatest operator.

Before that, we have to convert the ipcRenderer to an Observable. From this SO response we can do fromEvent(ipcRenderer,'data-from-container'). If it does not work we can use another subject that emits events each time we receive something in ipcRenderer.on(), the second solution requires ngZone.


ngOnInit() {
...

const containerData$ = fromEvent(this.electronService.ipcRenderer, 'data-from-container');
this.subscription = combineLatest(containerData$, this.viewInitialized$).subscribe(combined => {
  const data = combined[0];
  this.formData = data;
  this.prepopulateForm(data) 
})

...
}

ngOnDestroy() {
  this.subscription.unsubscribe();
}

Hope this helps.

Sauls answered 23/7, 2019 at 21:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.