0

I'm still trying to find an answer to Aurelia JS - Making a synchronous HTTP request, to change data before page load? - so I tried the following in the code example for that question, https://gist.run/?id=90d98563621fe49c1dde6b4f2fc6961d .

As per Aurelia - how to change bound variables, so the GUI changes?, I am aware that I can change a class variable that is a source of a HTML binding, and the HTML/GUI should update. So I'm trying something similar in the gist above - specifically, I am trying to change the contacts array property of the ContactList class (in contact-list.js).

Here are the relevant changes in app-clist.js:

import {WebAPI} from './web-api';
import {HttpClient} from 'aurelia-http-client';
import {ContactList} from './contact-list';
import {Container} from 'aurelia-dependency-injection';

// for multiline string, use backticks `` - ES6 template literals.
let phpcode = `
<?php
$outarr = array();

$tObj = new StdClass();
$tObj->{'id'} = '1';
$tObj->{'firstName'} = 'Bob';
$tObj->{'lastName'} = 'Glass';
$tObj->{'email'} = 'bob@glass.com';
$tObj->{'phoneNumber'} = '243-6593';
array_push($outarr, $tObj);
$tObj = new StdClass();
$tObj->{'id'} = '2';
$tObj->{'firstName'} = 'Chad';
$tObj->{'lastName'} = 'Connor';
$tObj->{'email'} = 'chad@connor.com';
$tObj->{'phoneNumber'} = '839-2946';
array_push($outarr, $tObj);

echo json_encode($outarr); 
?>
`;

export class AppClist { // in gist example is wrong, still called App
  static inject() { return [WebAPI, HttpClient, ContactList]; }

  constructor(api, http, conlist){
    this.api = api;
    this.http = http;
    this.conlist = conlist;
    var phpcodesl = phpcode.replace(/(?:\r\n|\r|\n)/g, ' ');
    var encphpcode = encodeURIComponent(phpcodesl); // urlencode
    //alert(encphpcode); 
    // NOTE: gist.run due https will not allow loading from http
    //this.http.post("https://phpfiddle.org/api/run/code/json", "code="+encphpcode )
    //.then(response => {alert(response.response); console.log(response);}) // does not work
    // this does work:
    console.log("a1", this.conlist, this.conlist.contacts);
    this.http.createRequest('https://phpfiddle.org/api/run/code/json')
     .asPost()
     .withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
     .withContent("code="+encphpcode)
     .send()
     .then(response => {
         alert(response.response);
         console.log(response);
         var respobj = JSON.parse(response.response);
         var respdataArr = JSON.parse(respobj.result);
         this.api.setContactList(respdataArr);
         console.log("a2", this.conlist, this.conlist.contacts, this.conlist.getThis(), Container.instance.get(ContactList));
     }).catch(err => {
         console.log(err); 
     })
    ;
  }
...

... and I added this console.log statement in contact-list.js:

  created(){
    this.api.getContactList().then(contacts => { 
      this.contacts = contacts;
      console.log("b1", this, this.contacts);  });
  }

... and also this function in contact-list.js:

  getThis(){
    return this;
  }

However, when I run this (after clicking the start "click me" button), I get this in the error log in Chromium browser:

VM2198 app-clist.js!transpiled:48

a1 ContactList {api: WebAPI, contacts: Array[0]} []
...

contact-list.js:21

b1 ContactList {api: WebAPI, __observers__: Object} [Object, Object, Object, Object, Object]
...

VM2198 app-clist.js!transpiled:55 

a2 ContactList {api: WebAPI, contacts: Array[0]} []
ContactList {api: WebAPI, contacts: Array[0]}
ContactList {api: WebAPI, contacts: Array[0]}
...

So, here is how I interpret this:

  • Message a1 is printed in constructor() of AppClist class - and it runs first; at that point, the ContactList class is made available through injection as a class property of AppClist called conlist. At this point, the AppClist.conlist.contacts (that is, ContactList.contacts) array is understandably empty, and has size 0.
  • Message b1 is printed when the ContactList component is created(), after the ContactList.contacts array has been initialized, and is printed second - again, as expected, there are 5 elements in the contacts array
  • Message a2 is printed when the HTTP call is finished - I would have expected 5 elements in the contacts array, but there are 0 (regardless of access method) ?!

So, my question is - why do I get 0 as size of the contacts array, when there should be at least 5? Does the inject maybe cache the state of the variable/class it is supposed to reference? How can I get a reference to the latest state of the contacts array property of ContactList class in the AppClist class?

Community
  • 1
  • 1
sdbbs
  • 4,270
  • 5
  • 32
  • 87

1 Answers1

0

Well, I think I found a fix - although this is all a bit of guesswork, so I would still appreciate a proper answer from someone.

First, I thought the issue was "caching", but it looks like it is far more likely, that for instance Container.instance.get(ContactList) returns a new instance of the class, rather than the one existing instance. Here are some relevant quotes I found:

Enhanced Dependency Injection Use · Issue #73 · aurelia/dependency-injection · GitHub suggests using this:

  // configure the container
  let container = aurelia.container;
  container.registerInstance(ApiClient, new ApiClient(isDebug));
  ...
  aurelia.start().then(a => a.setRoot());
  ...

... in main.js - I tried applying this to the ContactList class, but couldn't get my example to work...

If Aurelia understands "import", why use dependency injection?

This is because Aurelia's Dependency Injection container is instantiating an instance for you. ...
You are telling Aurelia "I need one of these, please give it to me," and Aurelia says "Sure thing, I've created one or I already had one lying around, here it is."

Hmm, well, in my example, by the time we get to the "a2" log, the DI should already "know" that it already had created one ContactList - but it apparently still creates a new object anyway...

How to create a singleton service in Aurelia?

By default, the DI container assumes that everything is a singleton instance; one instance for the app. However, you can use a registration decorator to change this.

Well, apparently it didn't assume that for ContactList in the example above ?!

The solution for me came from the other answer in the previous post, How to create a singleton service in Aurelia?:

So I realized I was thinking about this too hard. I was trying to depend on the framework (Aurelia) to do all the work, but actually it was a simple ES6 class change that makes it an instance.

... and here is how I applied that to the ContactList class, adding a cl_instance variable:

import {EventAggregator} from 'aurelia-event-aggregator';
import {WebAPI} from './web-api';
import {ContactUpdated, ContactViewed} from './messages';

let cl_instance = null;

export class ContactList {
  static inject = [WebAPI, EventAggregator];

  constructor(api, ea){
    if(!cl_instance) {
    cl_instance = this;

    this.api = api;
    this.contacts = [];

    ea.subscribe(ContactViewed, msg => this.select(msg.contact));
    ea.subscribe(ContactUpdated, msg => {
      let id = msg.contact.id;
      let found = this.contacts.find(x => x.id === id);
      Object.assign(found, msg.contact);
    });
    } // end if!
    return cl_instance;
  }
  ....

With this, apparently the ContactList class now behaves as a singleton (?!), and so everytime I ask for a reference to a class instance, I'll get the same class instance (and not instantiate a new one).

That means also that now this.conlist.contacts in AppClist refers to the actual datasource contacts property variable in ContactList, and thus assigning to it now triggers the binding and updates the GUI - which thus solves the problem in Aurelia JS - Making a synchronous HTTP request, to change data before page load? - I've saved that example for reference on https://gist.run/?id=f4bd01c99f9973cb76d8640f6248c2e3

Community
  • 1
  • 1
sdbbs
  • 4,270
  • 5
  • 32
  • 87