Search code examples
angulardata-bindingelectronsystemjszone.js

Electron + Angular 4 = two ways data binding not working


Each time we want to update the UI we are forced to use the zone.run() method and this in not an option since it's unintuitive and force devs to add more repetitive code.

Here our main dependencies:

  "dependencies": {
"@angular/animations": "4.0.0",
"@angular/common": "4.0.0",
"@angular/compiler": "4.0.0",
"@angular/compiler-cli": "4.0.0",
"@angular/core": "4.0.0",
"@angular/forms": "4.0.0",
"@angular/http": "4.0.0",
"@angular/platform-browser": "4.0.0",
"@angular/platform-browser-dynamic": "4.0.0",
"@angular/platform-server": "4.0.0",
"@angular/router": "4.0.0",
"@angular/upgrade": "4.0.0",
"@types/electron": "^1.4.34",

Here a snippet of our index.html :

    <!-- 1. Load libraries -->
<script src="../node_modules/zone.js/dist/zone.js"></script>
<script src="../node_modules/reflect-metadata/Reflect.js"></script>
<script src="../node_modules/systemjs/dist/system.src.js"></script>

<!-- 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
  System.import('app').catch(function(err){ console.error(err); });
</script>

Here the systemjs.config.js relevant part

(function (global) {
System.config({
    paths: {
        // paths serve as alias
        'npm:': '../node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
        // our app is within the app folder
        app: 'app',

        // angular bundles
        '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
        '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
        '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
        '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
        '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
        '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
        '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
        '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
        '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
        '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
        '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

        // other libraries
        'ng2-translate': 'npm:ng2-translate/bundles',
        'rxjs': 'npm:rxjs',
        'underscore.string': 'npm:underscore.string/dist',
        'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
    },

Now there is no error reported inside the Chrome dev console , but there are issues that are not working:

  • no http 404 so all js files are correctly download in the order expected (starting from index.html then all the .js refered by scripts tags then all the .js file refered by system.config.js)
  • The 2 ways data binding is not working and we are forced to inject zone.js inside the component constructor and use it's run() method to force updates on UI

Other relevent sources are the component and the html

Component and it's template (as we type inside a field we expect the form.value | json to display 'live' the content of the input text)

import { Component, OnInit, NgZone } from '@angular/core';
import { Router } from '@angular/router';

import { AuthenticationService, ILoginAjaxResponse } from './service/authentication.service';

export class Credentials {
    email: string;
    password: string;
}

@Component({
    selector: 'as-login',
    templateUrl: 'app/login/login.component.html',
})
export class LoginComponent implements OnInit {
    credentials = new Credentials();

    constructor(
        private authenticationService: AuthenticationService,
        private router: Router,
        private zone: NgZone) {
    }

    public ngOnInit() {
        // Test A
        // -------------------------------
        // this is visible in UI text box
        // this is visible in the UI {{credentials | json}} output
        // this is NOT visible in the UI {{form.value | json}} output
        this.credentials.email = 'john.doe@planet.com';

        // Test B
        // -------------------------------
        // this is NOT visible in UI text box
        // this is NOT visible in the UI {{credentials | json}} output
        // this is NOT visible in the UI {{form.value | json}} output
        setTimeout(() => {
            this.credentials.email = '11111111@planet.com'
        }, 3000);

        // Test C
        // -------------------------------
        // this is visible in UI text box
        // this is visible in the UI {{credentials | json}} output
        // this is visible in the UI {{form.value | json}} output
        setTimeout(() => {
            this.zone.run(() => {
                this.credentials.email = '22222222@planet.com'
            });
        }, 6000);
    }

    public onSubmit() {
        this.authenticationService.login(this.credentials)
            .subscribe((response) => this.handleResponse(response));
    }

    private handleResponse(response: ILoginAjaxResponse) {
        this.router.navigateByUrl('spa/sync')
            .catch((reason) => { console.log(reason); });
    }

}
<p>Form:</p>
<pre>
{{ form.value | json }}
</pre>

<p>Model:</p>
<pre>
{{ credentials | json }}
</pre>

<form #form="ngForm" novalidate>
    <input type="text" class="form-control"
        name="email" [(ngModel)]="credentials.email">

    <input type="password" class="form-control"
        name="password"  [(ngModel)]="credentials.password">

    <input class="btn btn-lg btn-primary btn-block"
        (click)="onSubmit()"
        type="submit" value="Submit">
</form>


Solution

  • Finally found the fix. This has nothing to do with the order or imports or tags inside index.html as I was originaly suspecting. This is related to This issue causing angular to drop out the zone. So the solution was to use Zone.current.wrap like mentioned in the issue workaround...