5

I am building an electron app with angular 2 in the renderer process. My main process talks to a socket server. Whenever the user is connected to this server or disconnected, I wish to show the user's status in the view.

For this, I use electron's ipc to send a message from the main to the renderer process, like this

socket.on('connect', function() {
  mainWindow.webContents.send('socket-connection-status', true);
});
socket.on('disconnect', function() {
  mainWindow.webContents.send('socket-connection-status', false);
});

In my view, I then have a (simplified) angular component, like this

const ipc = require('electron').ipcRenderer;
@Component({
  selector: 'status-bar',
  template: '{{status}}'
})
export class StatusBarComponent {
  private status: string = "offline";
  constructor() {
    ipc.on('socket-connection-status', function(event, status) {
      if (status===true) {
        this.status = 'online';
      } else {
        this.status = 'offline';
      }
      console.log(status);  // Successfully logs true|false
      console.log(this.status);  // Successfully logs online|offline
    })
  }
}

I successfully log the messages from main process.

The problem is that angular 2 does not 'know' electron's ipc, so change detection is not triggered for status. I have seen several people struggling with this issue, but haven't come across a 'true' solution.

I have tried to solve it with injecting ApplicationRef, ChangeDetectorRef and ngZone (ref: Triggering Angular2 change detection manually), but none of the methods provided (tick(), detectChanges(), run() respectively) happened to provide a solution.

Apparently, 'within' ipc.on I cannot reference my class' properties/methods/injectables as I run into errors: For instance, this (https://github.com/JGantner/angular2_change_detection_issue/blob/master/browser/security-level-indicator-component.ts) solution (which I find not very elegant) results in Uncaught TypeError: Cannot read property 'markForCheck' of undefined.

Could somebody please help me out with how to make change detection work in my case?


Edit (hack):

One way I have found in which I at least get the functionality that I need/want:

status-bar.component.ts:

const ipc = require('electron').ipcRenderer;
import { SocketStatusService } from '../services/socket-status.service';
@Component({
  selector: 'status-bar',
  template: '{{status}}'
})
export class StatusBarComponent {
  private status: string = "offline";
  status$: Subscription;
  constructor(private socketStatusService: SocketStatusService, private ref: ApplicationRef) {
  ipc.on('socket-connection-status', function(evt, status) {
    if (status===true) {
      this.service.updateSocketStatus('online');
    } else {
      this.service.updateSocketStatus('offline');
    }
  }.bind({service: socketStatusService}))

  this.status$ = this.socketStatusService.socket_status$.subscribe(
    status => {
      this.status = status;
      this.ref.tick();
    }
  )
}

socket-status.service.ts:

@Injectable()
export class SocketStatusService {
  private socket_status = new BehaviorSubject<string>("offline");
  socket_status$ = this.socket_status.asObservable();

  updateSocketStatus(status: string) { 
    this.socket_status.next(status);
  }
}

Although this works, I have the feeling there must be a more elegant way to achieve this behavior.

Best case scenario though would be a means to set the component's class properties directly in the ipc callback and trigger change detection... So far I have not been able to get that to work, so any help would be appreciated.

(p.s. Also, I am not sure why I have to manually trigger this.ref.tick(), which is not something I remember having had to do to trigger change detection from streams in earlier beta versions of angular 2...)

Community
  • 1
  • 1
Willem van Gerven
  • 1,407
  • 1
  • 17
  • 24
  • 1
    Change detection may be possibly triggered by wrapping the code with `setTimeout`. `markForCheck` doesn't work because event handler isn't an arrow, thus `this` isn't lexical, this is a common mistake. Did the other things you've tried (`tick()`, `detectChanges()`, `run()`) have the same problem? – Estus Flask Oct 30 '16 at 17:03
  • Yes, whatever I inject and reference inside of `ipc.on(,...)` throws a problem. For instance, I thought that perhaps creating a service with the status as BehaviorSubject and then subscribing to it might help me out. But in that case too I get `TypeError: Cannot read property 'updateSocketStatus' of undefined`. `updateSocketStatus` is the method that sends a new value down the stream. So basically my problem is how to handle data in general that is being sent from main to renderer... `this.status` logs the right value within `ipc.on` (as in the post), but outside of it, it remains `offline`. – Willem van Gerven Oct 30 '16 at 19:23
  • If you've just set `this.status`, then sure, it will be set when you're logging `this.status`. The problem is that it will be set on wrong object. – Estus Flask Oct 30 '16 at 19:37
  • Ok, I've found an 'intermediate' solution. The `TypeError` problem appears to be a problem with scope; I have to bind my instance to the callback, i.e., `ipc.on('...', (evt, status)=>{...}.bind({service: socketStatusService}))`, and then use `this.service` inside the callback. Like this I can use a service to broadcast, and then I can observe and trigger change detection. What would be the ultimate solution though is to be able to set class properties right within the callback and trigger change detection, without the need for a service. I would be eager to know how to do this. – Willem van Gerven Oct 30 '16 at 21:16
  • I'm not sure what's the deal with `socketStatusService`, it isn't in your question. `(evt, status)=>{...}.bind({service: socketStatusService}))` makes no sense because arrow functions cannot be bound, it's just `(evt, status)=>{...}`. – Estus Flask Oct 30 '16 at 21:27
  • It's just a 'hack' to get the functionality that I want to work... Please see my edit of the original post. – Willem van Gerven Oct 30 '16 at 21:45
  • @estus yes, sorry, that arrow function was a typo. Like I said, please see the edit in original post. – Willem van Gerven Oct 30 '16 at 21:51
  • 1
    The service looks just redundant to me here. Do `ipc.on('...', (evt, status) => { ...; this.changeDetectorRef.detectChanges() })` or `ipc.on('...', (evt, status) => setTimeout(() => { ... }))` not work for you? – Estus Flask Oct 30 '16 at 21:56
  • Rediculous... Your first (I haven't tried the second) option works like a charm... I tried exactly the same, but like this `ipc.on('...', function(evt, status) { ...; this.changeDetectorRef.detectChanges() })`, and that does throw me the `TypeError`. So apparently I will have to read up more on the difference between fat arrows and function syntax. Thank you so much for helping out! – Willem van Gerven Oct 30 '16 at 22:07
  • @estus perhaps you'd like to put your solution in a post, so I can accept it. – Willem van Gerven Oct 30 '16 at 22:08
  • If this works for you, then sure, I will (I don't have Electron at hand to test it). – Estus Flask Oct 30 '16 at 22:33
  • Yes, you provided the solution! – Willem van Gerven Oct 30 '16 at 22:43

1 Answers1

6

this.status is set on wrong object, and markForCheck doesn't work because of this too. Event handler should be an arrow in order for proper context to be provided.

With proper this context any known way to trigger change detection should work.

For example

ipc.on('...', (evt, status) => {
  ...
  this.changeDetectorRef.detectChanges();
});

Or

ipc.on('...', (evt, status) => {
  setTimeout(() => {
    ...
  });
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • So my problem in the end was using `function(evt, status){...}` syntax for the callback, which throws a `TypeError` that `changeDetectorRef` is undefined. I guess that the other methods then of `NgZone` and `ApplicationRef` one should also be able to get working like this. – Willem van Gerven Oct 31 '16 at 07:28
  • 1
    That's correct. I guess any of them would work. In TS and ES6 arrows are a must for callbacks for this reason (including event handlers, promise callbacks and anything else). – Estus Flask Oct 31 '16 at 07:43