I've created a wrapper class for S3 operations but when I compile using the TypeScript complier I get the following error:
lib/Store.ts:20:15 - error TS2531: Object is possibly 'null'.
20 await this.store.upload({
I'm new to TypeScript but I understand TypeScript is doing it's job here and preventing the possibility that await this.store.upload() could run while this.store is null. What's the correct way for dealing with this type of situation where a class value might not yet be initialised?
My wrapper class:
import S3 from 'aws-sdk/clients/s3';
export class Store {
storeName: string;
store: S3 | null;
initialised: boolean;
constructor(storeName: string) {
this.storeName = storeName;
this.store = null;
this.initialised = false;
}
_init(): void {
this.store = new S3();
}
async _writeToStore(data: object, path: string): Promise<void> {
if (!this.initialised) this._init();
await this.store.upload({
Bucket: this.storeName,
Key: path,
Body: data
}).promise();
}
}
I've always tried to avoid creating new instances of classes in the constructor because it's awkward to mock.
Maybe passing a new class instance into the constructor is the best approach?
Typescript is giving you that error because you have strictNullChecks enabled and your property store has null as a possible type.
You can do any of these options
Option 1 - Remove null type
You can probably drop the null type on store, since you are setting the value of that property on your constructor, and nothing on your code sets it to null:
store: S3;
Option 2 - Add non-null assertion operator
Alternatively, if this.store is never going to be null when you execute this.store.upload({...}), then you can add a non-null assertion operator (!) like this:
this.store!.upload({...})
That will tell Typescript to stop giving the error. Note that this doesn’t change the runtime behavior of your code, so it's important to only use ! when you know that the value can't be null or undefined.
Option 3 - Check store for null value right before
You can explicitly check this.store for null value right before calling this.store.upload(). But this call must be done within the same method, like this:
if (!this.store) {
this.store = new S3();
}
await this.store.upload({...});
This won't work:
if (!this.store) {
this._init();
}
await this.store.upload({...});
Conclusion
Personally, I would go with option 1. I'm assuming that your reason for writing a wrapper for S3 is so that your consumers never have to instantiate and work directly with an S3 object and instead instantiate and work with your wrapper class/object.
You can try
{
...
"strictNullChecks": true,
"strictPropertyInitialization": true,
...
}
"strictNullChecks" tells the compiler to watch for any declared variables that evaluate to null or undefined and raise on error on at compiler time (https://www.typescriptlang.org/tsconfig#strictNullChecks)
"strictPropertyInitialization" tells the compiler to raise an error 'when a class property was declared but not set in the constructor'.
(https://www.typescriptlang.org/tsconfig#strictPropertyInitialization)
When getting Data from a VueX Module via Getter the Object is wrapped with a Proxy that throws following error:
TypeError: attempted to get private field on non-instance
when trying to read a private property with a public getter.
class Example {
#privateField;
get privateField() {
return this.#privateField;
}
}
computed(){
getInfoForSpecificItem() {
const arrayOfInstantiatedExamples = this.$store.getters['getArrayOfInstantiatedExamples'];
for (let i = 0; i < arrayOfInstantiatedExamples.length; i += 1) {
// trying to access it in this condition throws the error since the Object
// has been wrapped with a Proxy coming from Vue (I guess)
if (arrayOfInstantiatedExamples[i].privateField === 'something') {
// does something
}
}
}
}
Why am I not able to read the property, and how can I create an instance from the proxy target.
logging the Proxy Object reveals, that the target has all information, yet trying to read it via getter doesn't work.
Are there any other options than making the fields public?
You are defining a class not a function. So remove the parentheses().
class Example {
You cannot define variable with #. Only $ and _ are allowed to use in a variable. So replace # with $ like this $privateField;
Classes are blueprints, not a valid object. First make an object from this class.
const proxyWrappedExample = new Example;
After you log this then you can avoid the error but it logs undefined.
Your code should look like
class Example {
$privateField = 'something';
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'something'
$privateField without any value will look like following.
class Example {
$privateField;
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'undefined'
I'm doing a get call to my API and returning a list of an object. I'm then assigning that data to a property in my DataService so I can use it across components.
Here is the code in my component calling the service:
getAvailableParameters() {
this.verificationService.getAvailableParameters(this.itemNumbers).subscribe((res => {
this.dataService.availableParameters = res;
}))}
Here is the call to the API
getAvailableParameters(itemNumbers: string[]){
return this.http.get<BaseResponse<IavailableParameters[]>>(`${this.baseUrl}api/verification/${itemNumbers}`)
}
Here is the property in the DataService that should store the object after getAvailableParameters is called:
export class DataService {
public orderInfo: Iorder;
public availableParameters: IavailableParameters[];
}
As you can see, the object coming back from the API is also wrapped in a BaseResponse object (which I believe to be the problem but don't know how to resolve), Defined here:
import { Error } from './Error'
export interface BaseResponse<T> {
isSuccessful: boolean,
error: Error;
results: T;
}
This is the exact error I'm getting:
ERROR in src/app/Components/verification-reply/verification-reply.component.ts(28,7): error TS2740: Type 'BaseResponse<IavailableParameters[]>' is missing the following properties from type 'IavailableParameters[]': length, pop, push, concat, and 26 more.
When I remove the base response type, it works fine, as I'm assuming it doesn't register that as an array or something. This binding method to the data service object worked fine when the object I returned was not an array.
I am inexperienced in this subject and could use some help figuring out how to resolve this error.
When I remove the base response type, it works fine, as I'm assuming it doesn't register that as an array or something.
That is to be expected because BaseResponse<IavailableParameters[]> is not an an array. It is an object with properties isSuccessful, error, and results. The property results is an array IavailableParameters[].
You need to make sure that whatever type you are providing to this.http.get matches the actual type that is returned from the request. Is it an array of IavailableParameters? Or an object with isSuccessful, error, and results?
If you have a variable response of type BaseResponse<IavailableParameters[]> and you are trying to use it as an array IavailableParameters[], like in the error message that you posted, then just use response.results instead of the entire response object.
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.
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