Aurelia - accessing updated class properties from injection? - javascript

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?

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

Related

Angular 2 array after subscribe has elements but length is 0

I'm using Angular 2 and I have noticed an unexpected behaviour.
I have a datasource class, which extends DataSource, with two variables:
private archives = new BehaviorSubject<MyArchive[]>([]);
public archives$: Observable<MyArchive[]> = this.archives.asObservable();
private filteredArchive: MyArchive[];
I update archives this way within the class:
this.archives.next(this.filteredArchive);
Outside in another class I try to subscribe to the observable but it doesn't work:
ngAfterViewInit(): void {
this.dataSource.archives$.subscribe((archive: Array<MyArchive>) => {
console.log(archive.length);
console.log(archive);
console.log(archive.length);
}
}
In the console log it prints:
0
<THE ARCHIVE WITH AN OBJECT INSIDE AS IT SHOULD BE, WITH LENGTH = 1>
0
So that I can't iterate on the archive variable because its length is 0. What's going on here?
the issue this the value is already emitted before you subscribe. Think of observables like tv stream and your data is like a show on this stream if you open the tv after the show ended (subscribe after you pushed the data) you never see the show. if you want your observable to keep the last value you can use the Scan operator like this :
export const reducer = () =>
scan<MyArchive[]>((archive, update) => {
//update your Archive or simply overwrite the value
archive = update
return archive
}, []);
export class datasource {
archives$ : Observable<MyArchive[]>;
archives : Subject<MyArchive[]> = new Subject([]);
update(newMyArchive: MyArchive[]) {
this.archives.next(newMyArchive);
}
constructor(public http: HttpClient) {
this.archives$ = this.archives.pipe(
reducer(),
//use the shareReplay so all your subscribers get the same values
shareReplay(1)
);
this.archives$.subscribe();
}
and you can update the Arcjive useing the update method in the datasource class like:
this.update(filteredArchive)
I'm not sure why you're redundantly creating archives$ and archives. You can do it with just a single BehaviorSubject.
I think you should be doing this:
// Create a BehaviorSubject for MyArchive[]
public archives$: BehaviorSubject<MyArchive[]> = new BehaviorSubject<MyArchive[]>([]);
// Create a private filtered Array for MyArchive.
private filteredArchive: MyArchive[];
And then,
this.archives$.next(this.filteredArchive);
When you use this statement:
console.log(archive);
a reference to the archive object is shown in the console (see the MDN documentation). The content of the object can be different when you inspect it in the console (by clicking on the arrow) from what it was when the console.log statement was executed.
In order to see what the actual content is at a specific moment, you should output a string representation of the object:
console.log(archive.toString());
console.log(JSON.stringify(archive));
You can expermiment with this stackblitz to see the various console outputs.
I solved the problem.
I forgot that this.filteredArchive is updated by an HTTP GET request. I was trying to push data within this.filteredArchive using forEach, but I forgot to call then() when all the data are received.
Hence I was trying to access, using subscribe, a result that was not yet ready.
Thanks everyone for the replies!

Execute typescript methods when loading from API or TEXT?? Angular2/4 etc

I have created some functions on my objects, but these do not seem to register with angular/typescript... I think i know the reason why, but i dont know how to fix it, or maybe i am doing something wrong!
I have added an example here
This is a very simple example, but shows the problem i am having. If you open up the console, you will be able to see the exception i am talking about later.
So, in this example, i have 2 classes. MyParent and MyChild and they look like this
import { MyChild } from './MyChild'
export class MyParent {
id: number;
name: string;
children: MyChild[] = [];
addChild() {
this.children.push(new MyChild());
}
constructor() {
this.id = 1;
this.name = 'parent object!';
}
}
export class MyChild {
id: number;
name: string;
constructor() {
this.id = 0;
this.name = 'new child';
this.changeName = () => {
this.name = new Date().toString();
}
}
}
Now all i want to do is be able to load this data from HTTP request, and call the method on MyParent object and method on MyChild object (example uses JSON.parse() as httpClientModule not available on webpackbin)
If you press the button "First parent Method", you will notice a child object is added to the first Parent object. Great!
If you press the button "First child Method", the first child object in the collection has had its name changed! Great!
Now if you do the same for the second object, by pressing the buttons "Second parent method" and "Second child method" you get exceptions of
ERROR TypeError: item.addChild is not a function
AND
ERROR TypeError: item.changeName is not a function
Why? This object was created from text, so wonder if that has got something to do with it? But even if i change this line in the app.component.ts file i still get the same errors
this.second = JSON.parse(this.json);
to this
this.second = JSON.parse<MyParent>(this.json);
I can make the first parent function work if i change the code that loads the JSON data to use Object.assign, but this cannot be the fix, because although the parent method works, the child method does not!
this.second = Object.assign(new MyParent(), JSON.parse<MyParent>(this.json));
This example is just to show that the error happens. As i said, i want to use the HttpClientModule to load my data from an API, and even when i use the following code, it errors like above
this.httpClient.get<MyParent>('');
So i can only assume it is the same problem, and i am doing something wrong.
I can get round this problem on the parent object, is i used Object.assign
Help much appreciated.

new object in constructor from class undefined

I'm creating a new object from a class in a constructor, and whenever it runs I get an error that operate is undefined in the method, though it is defined in the constructor. Operate itself is thoroughly tested and works great in a separate context so that's not the problem. I'm building it with Babel, not running it directly in Node 7.0.0
import Operate from "./operate"
export default class {
constructor(Schema) {
this.schema = Schema
this.operate = new Operate(this.schema)
console.log(this.operate.run) // <- Logs just fine
}
update(req, res) {
console.log(this.operate.run) // <- Nada
this.operate.run(req.body)
.then(value => {
res.status(200).json(value)
})
}
This feels like I'm missing something fundamental. I've heard this isn't a great pattern anyway, so please feel free to suggest a better way. Thanks so much in advance.
UPDATE: This is how update is being used. I don't suspect there's any problem here, as it has worked just fine when I had been importing controller as a function from another module, instead of a class
import {Router, } from "express"
import Controller from "../controller"
import User from "./user.model"
let controller = new Controller(User)
let router = new Router()
router.post("/", controller.update)
module.exports = router
Change from this:
router.post("/", controller.update)
to this:
router.post("/", controller.update.bind(controller))
When you pass controller.update it only passed a pointer to the method and any association with the controller object is lost. Then, when that update method is called later, there is no association with the appropriate object and thus the this handler in the method is wrong and you get the error you were seeing.
You either force the binding of the update method within the object or when you pass the method elsewhere that might not be called correctly, you can use the above structure to pass a bound version of the method.
You could also modify your definition of the update method to permanently bind it to your object in the constructor by adding this to the constructor:
this.update = this.update.bind(this);

Class and scope in ember js

Im building an ember application consuming a couple of web services. And I'm trying to pass a class object throw the config/environment file doing this:
var myclass = require('myclass-package');
var ENV = {
APP: {
MY_OBJ_CLASS: new myclass({
//CONSTRUCTOR PARAMS...
PROP1: "HELLO"
})
}
}
In my ember app/controllers I'm doing this:
import ENV from '../config/environment';
var obj1 = ENV.APP.MY_OBJ_CLASS;
I can see that the object is instantiated if I console.log the class object but when I try to access to the properties and functions, I can't and I get back this error:
var data = obj1.my_function_class({param1:1});
console.log(data)
TypeError: obj1.my_function_class is not a function
But the function exist...
What is the way to access to my class properties and functions?
config/environment.js is a special file. It is executed in Node, then serialized to be made available for the browser app.
You should not store any functionality in that file.
Put your class into a proper Ember module. Depending on what you're trying to achieve, that could be a service, a model, an util, etc.
Provide more details on your original problem, not your attempted solution. See http://xyproblem.info .

Using ES6 classes OR Object Literals in controllers for an Express + NodeJS app

There are 2 things that I'm very confused about.
What is the advantage of using any of ES6 class or Object literals.
And where should I use any of them?
Some of the examples that I'm trying out are mentioned below. Please let me know when to use particular way of implementation and when not to.
Class Example 1:
// auth.js
class Auth {
login(req, res) {...}
signup(req, res) {...}
}
module.exports = new Auth();
// index.js
const auth = require('auth');
Class Example 2:
// auth.js
class Auth {
login(req, res) {...}
signup(req, res) {...}
}
module.exports = Auth;
// index.js
const Auth = require('auth');
const auth = new Auth();
Object Literal Example:
// auth.js
module.exports = {
login: (req, res) => {...},
signup: (req, res) => {...}
};
// index.js
const auth = require('auth');
What I think from reading about them is that:
Class Example 1:
You can not create more than 1 object. Because a module is only executed once. So, on every import you will get the same object. Something similar to singleton. (Correct me here if I misunderstood it)
You will not be able to access the static methods of the class because you are only exporting the object of the class.
Class Example 2:
If you have a class that contains nothing but helper methods and the object does not have any state, It makes no sense creating object of this class all the time. So, in case of helper classes, this should not be used.
Object Literal Example:
You can not do inheritance.
Same object will be passed around on every require. (Correct me if I'm wrong here as well)
Please help me understand these concepts, what I'm missing out, what I've misunderstood and what should be used when and where. I'll be very grateful for your help.
Feel free to edit the question, if you think I made a mistake somewhere.
Class Example 1: You can not create more than 1 object. Because a module is only executed once. So, on every import you will get the same object. Something similar to singleton.
Correct. This is an antipattern therefore. Do not use it. class syntax is no replacement for object literals.
You will not be able to access the static methods of the class because you are only exporting the object of the class.
Theoretically you can do auth.constructor.… but that's no good.
Class Example 2: If you have a class that contains nothing but helper methods and the object does not have any state, It makes no sense creating object of this class all the time. So, in case of helper classes, this should not be used.
Correct. Use a simple object literal instead, or even better: multiple named exports instead of "utility objects".
Object Literal Example: You can not do inheritance.
You still can use Object.create to do inheritance, or parasitic inheritance, or really anything.
Same object will be passed around on every require.
Correct, but that's not a disadvantage. If your object contains state, you should've used a class instead.
If your class has got a constructor, you can build several objects from this class threw :
var Panier= require('./panier');
var panier1 = new Panier(13, 12, 25);
var panier2 = new Panier(1, 32, 569);
Of course your Panier would be defined in the file Panier.js located in the same directory :
module.exports = class Panier
{
constructor(code, qte, prix)
{
this.codeArticle = code;
this.qteArticle = qte;
this.prixArticle = prix;
}
getCode()
{
return this.codeArticle;
}
getQte()
{
return this.qteArticle;
}
getPrix()
{
return this.prixArticle;
}
}

Categories