Angular 6 HttpClient.get Observable does not assign value - angular6

I suppose that the answer will be very obvious, but still it evades me. I'm new on working with observables, and now I'm facing issues assigning a value from one. I had success if I define it (this._apps) as an Observable and asking from the view to the service using subscribe (But for my taste is was way convoluted (three levels inside a map just to return another observable with the array and then another function to subscribe the previous to assign the variable and another subscription in the view to finally show the information), inefficient and on top of that I could not get it "right" again). The task is very simple. Given the class Application
export class Application {
name: string;
baseUrl: string;
deprecated: boolean;
}
And the service (just the relevant code)
private _apps: Application[] = [];
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('Apps subscriber');
this._apps = apps;
console.log('Apps subscriber Ends ' + apps);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
private getAllApplications() {
return this._http.get<Application[]>('http://development:4300/api/v1/apps');
}
From the constructor the function which gets the information from WebAPI is triggered, and the remote call is successful, but the variable this._apps is an empty array if I try to call it from anywhere in the code. I could not determine the type of the parameter "apps" in the subscribe function, but for some reason it cannot be assigned and the best answer given is that it is a function (See my first update) in one of my tries. Currently it returns in the console "[object Object]", but apps[0] gives undefined, so it is an empty Array.
This is the console output, just starting the application:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Refreshing apps cache calling http://development:4300/api/v1/atbc-apps
Apps subscriber
Apps subscriber Ends [object Object]
I was trying this solution among many others that I forget (to use the more modern HttpClient instead the Http I used before), so what I'm doing wrong?
Update 1
I changed the constructor to this:
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('apps length ' + apps.length);
this._apps = apps; // Remember private _apps: Application[] = [];
console.log('Apps subscriber Ends ' + apps.toString);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
and the declaration of the function called into this:
private getAllApplications(): Observable<Application[]> {
// the exactly the same as before
}
And now I got from the console this:
apps length undefined
Apps subscriber Ends
function () {
if (this instanceof Promise) {
return PROMISE_OBJECT_TO_STRING;
}
return originalObjectToString.apply(this, arguments);
}
That is the function I was talking about. Any ideas about why even though there is no errors (nor at compile time, neither at runtime), the returning object is not a real Application array?

Change this line:
private _apps: Application[] = [];
to:
_apps: Application[] = [];
Which will default to making it public. Then this line will see it:
this._apps = apps;

At the end I suppose is a mindset to work with Observables, and I tried to build a kind of cache, so the only way I could do it (let me know if there is a better way) was using the view to fill-out the cache. I could not do it from the service itself because the calling the function from the view is synchronous and to fill out the array is async. So I had to create a public setApplicationCache procedure which is filled out after calling the service from the view, it call the setApplicationCache( Application[] ) function and the rest works because it takes just the cache to do filtering and other operations or use it from other pages w/o calling the database again and again.
This is the code from the first view called (main page)
ngOnInit() {
this._myService.getAllApplications().subscribe(arrObjApps => {
this._myService.setApplicationsCache(arrObjApps)
this.listApps = this._myService.getApplications(true);
});
And the service has this functions:
private _apps: Application[] = [];
getAllApplications(): Observable<Application[]> {
return this._http.get('http://development:4300/api/v1/atbc-apps').pipe(
map( (response: Response) => {
let results = response.json().data.map( app => {
return new Application(app.name, app.baseUrl, app.deprecated);
});
return results;
})
);
}
getApplication(appName: string): Application {
return this._apps.find(app => app.name == appName);
}
getApplications(onlyActives: boolean): Application[] {
if (onlyActives) {
return this._apps.filter(app => app.deprecated == false);
} else {
return this._apps;
}
}
And as I stated the solution should be obvious. Just again the async mindset required to work with observables.

Related

Razor Component Call to HttpClient Not responding

I'm trying to call an injected HttpClient during operations within a Razor Component. When I do so during OnInitialized, the return is as expected. When I do so on an event like an input change, the client call doesn't respond.
I'm using a mix of MVC Controllers/Views with Razor Components in .Net Core 3.1.
Startup.cs
services.AddControllersWithViews()...
services.AddRazorPages()...
services.AddHttpClient<IJiraService, JiraService>("jira", c =>
{
c.BaseAddress = new Uri(Configuration.GetSection("ApplicationSettings:Jira:Url").Value);
var auth =
$"{Configuration.GetSection("ApplicationSettings:Jira:UserName").Value}:{Configuration.GetSection("ApplicationSettings:Jira:Password").Value}";
var authHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth));
c.DefaultRequestHeaders.AddAuthorization("Basic", authHeaderValue);
c.Timeout = TimeSpan.FromSeconds(20);
c.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue
{
NoCache = true
};
});
ChildComponent.razor
#inject IJiraService JiraService
#code {
public int SelectedReleaseId
{
get => ReleaseModel.SelectedReleaseId;
set
{
ReleaseModel.SelectedReleaseId = value;
ReleaseChanged().Wait();
}
}
}
#functions
{
private async Task ReleaseChanged()
{
if (ReleaseModel.SelectedReleaseId > 0)
{
var url = "...";
await JiraService.GetResponseAsync(url);
}
}
JiraService.cs
public async Task<string> GetResponseAsync(string url)
{
var resp = await httpClient.GetAsync(url); // <--- this is the call that never returns when invoked from an input control event
var respContentString = await resp.Content.ReadAsStringAsync();
if (resp.StatusCode != HttpStatusCode.OK)
{
throw new HttpOperationException(
$"Invalid response from Jira service: {resp.StatusCode}: {respContentString}");
}
return respContentString;
}
There's actually a bit of service classing in between, but this is the jist.
I've abstracted the call up to a parent component and implemented EventCallbacks all with the same result. The underlying call in the JiraService gets hit and i see a breakpoint stop on the await httpClient.GetAsync(url); but then execution just goes into the ether. There's not even an exception thrown or timeout.
It all seems so obvious now. The problem was a deadlock. This old post helped me realize that my property based #bind attribute was synchronously calling into an async/await graph. I refactored this into an #onchange function that enabled appropriate async/await behavior through the call stack and viola, await httpClient.GetAsync() behaved just like it should.
A little annoyed at the #bind behavior that takes the onchange event functionality in addition to the property value.

Why undefined is displayed instead of object - (Angular HTTP request)

I want to get an array from Spring Boot API and I can't convert data into object properly
It's my model:
export class Uczen {
constructor(
public firstname: string,
public lastname: string,
public email: string,
public gender: string,
) { }
}
service:
getPersons() {
return this.http.get('http://localhost:8080/myApp/persons');
}
It's my component.ts code
osoby: Uczen[] = [];
ngOnInit() {
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
});
console.log(this.osoby[1]);
console.log(this.osoby.length);
}
im getting "undefined" and "0" as display,there is problem with conversion json to object array prodably ;/
consoles should be inside the subscription since this is an asynchronous procedure
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
console.log(this.osoby[1]);
console.log(this.osoby.length);
});
In your ts file, you have ngOnInit() method, ngOnInit is component's life cycle hook which runs when component is being initialized.
1) Initially the control calls the getPersons() method and it will take some time (some milli seconds) to get response from your server.
2)Due to asynchronous behaviour, before we get response from remote server the control goes to the console statement line and it is executed.
At this point of time the variable osoby is still an empty array, which is why you are getting undefined , accessing first element of empty array.
3)If you write the same console lines inside subscription, the control to those lines will go only after receiving the response from your server .
this.personService.getPersons().subscribe((result: Uczen[]) => {
for (const item of result) {
this.osoby.push(item);
}
console.log(this.osoby[1]);
console.log(this.osoby.length);
});

Rx Subscribe OnComplete fires but cannot use the data

I have a product service with a method that returns an Observable from json data.
product.service.ts....
getProducts(): Observable<IProductData[]> {
return this._http.get(this._productUrl)
.map((response: Response) => <IProductData[]> response.json())
.catch(this.handleError);
}
In my component I subscribe to the serivce and call the getProducts() method returning the Observable IProductData[].
mycomponent.ts ..
productData: IProductData[];
ngOnInit(): void {
this._productService.getProductsObservable()
.subscribe(
productData => this.productData = <IProductData[]>productData,
error => this.errorMessage = <any>error,
function() { console.log(this.productData ) }
);
}
When I review the onCompleted console.log this.productData is undefined!
I want to use this data to setup the fields in my component. When/how can I be sure that the data has been returned?
If I output this.productData on a button click event the data has been populated, but I want to do this on init.
Any suggestions or advice would be much appreciated.
cheers
You are not using an arrow function in the final block so your this is local to the complete function that you pass in.
In addition to appearances there are actual semantic differences between () => {} and function() {}
Read here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
...Until arrow functions, every new function defined its own this value (a new object in case of a constructor, undefined in strict mode function calls, the context object if the function is called as an "object method", etc.). This proved to be annoying with an object-oriented style of programming.
In ECMAScript 3/5, this issue was fixed by assigning the value in this to a variable that could be closed over.
An arrow function does not create its own this context; rather, it captures the this value of the enclosing context...

async constructor functions in TypeScript?

I have some setup I want during a constructor, but it seems that is not allowed
Which means I can't use:
How else should I do this?
Currently I have something outside like this, but this is not guaranteed to run in the order I want?
async function run() {
let topic;
debug("new TopicsModel");
try {
topic = new TopicsModel();
} catch (err) {
debug("err", err);
}
await topic.setup();
A constructor must return an instance of the class it 'constructs'. Therefore, it's not possible to return Promise<...> and await for it.
You can:
Make your public setup async.
Do not call it from the constructor.
Call it whenever you want to 'finalize' object construction.
async function run()
{
let topic;
debug("new TopicsModel");
try
{
topic = new TopicsModel();
await topic.setup();
}
catch (err)
{
debug("err", err);
}
}
Readiness design pattern
Don't put the object in a promise, put a promise in the object.
Readiness is a property of the object. So make it a property of the object.
The awaitable initialise method described in the accepted answer has a serious limitation. Using await like this means only one block of code can be implicitly contingent on the object being ready. This is fine for code with guaranteed linear execution but in multi-threaded or event driven code it's untenable.
You could capture the task/promise and await that, but how do you manage making this available to every context that depends on it?
The problem is more tractable when correctly framed. The objective is not to wait on construction but to wait on readiness of the constructed object. These are two completely different things. It is even possible for something like a database connection object to be in a ready state, go back to a non-ready state, then become ready again.
How can we determine readiness if it depends on activities that may not be complete when the constructor returns? Quite obviously readiness is a property of the object. Many frameworks directly express the notion of readiness. In JavaScript we have the Promise, and in C# we have the Task. Both have direct language support for object properties.
Expose the construction completion promise as a property of the constructed object. When the asynchronous part of your construction finishes it should resolve the promise.
It doesn't matter whether .then(...) executes before or after the promise resolves. The promise specification states that invoking then on an already resolved promised simply executes the handler immediately.
class Foo {
public Ready: Promise.IThenable<any>;
constructor() {
...
this.Ready = new Promise((resolve, reject) => {
$.ajax(...).then(result => {
// use result
resolve(undefined);
}).fail(reject);
});
}
}
var foo = new Foo();
foo.Ready.then(() => {
// do stuff that needs foo to be ready, eg apply bindings
});
// keep going with other stuff that doesn't need to wait for foo
// using await
// code that doesn't need foo to be ready
await foo.Ready;
// code that needs foo to be ready
Why resolve(undefined); instead of resolve();? Because ES6. Adjust as required to suit your target.
Using await
In a comment it has been suggested that I should have framed this solution with await to more directly address the question as asked.
You can use await with the Ready property as shown in the example above. I'm not a big fan of await because it requires you to partition your code by dependency. You have to put all the dependent code after await and all the independent code before it. This can obscure the intent of the code.
I encourage people to think in terms of call-backs. Mentally framing the problem like this is more compatible with languages like C. Promises are arguably descended from the pattern used for IO completion.
Lack of enforcement as compared to factory pattern
One punter thinks this pattern "is a bad idea because without a factory function, there's nothing to enforce the invariant of checking the readiness. It's left to the clients, which you can practically guarantee will mess up from time to time."
If you take this position then how will you stop people from building factory methods that don't enforce the check? Where do you draw the line? For example, would you forbid the division operator because there's nothing stopping people from passing a zero divisor? The hard truth is you have to learn the difference between domain specific code and framework code and apply different standards, seasoned with some common sense.
Antecedents
This is original work by me. I devised this design pattern because I was unsatisfied with external factories and other such workarounds. Despite searching for some time, I found no prior art for my solution, so I'm claiming credit as the originator of this pattern until disputed.
Nevertheless, in 2020 I discovered that in 2013 Stephen Cleary posted a very similar solution to the problem. Looking back through my own work the first vestiges of this approach appear in code I worked on around the same time. I suspect Cleary put it all together first but he didn't formalise it as a design pattern or publish it where it would be easily found by others with the problem. Moreover, Cleary deals only with construction which is only one application of the Readiness pattern (see below).
Summary
The pattern is
put a promise in the object it describes
expose it as a property named Ready
always reference the promise via the Ready property (don't capture it in a client code variable)
This establishes clear simple semantics and guarantees that
the promise will be created and managed
the promise has identical scope to the object it describes
the semantics of readiness dependence are conspicuous and clear in client code
if the promise is replaced (eg a connection goes unready then ready again due to network conditions) client code referring to it via thing.Ready will always use the current promise
This last one is a nightmare until you use the pattern and let the object manage its own promise. It's also a very good reason to refrain from capturing the promise into a variable.
Some objects have methods that temporarily put them in an invalid condition, and the pattern can serve in that scenario without modification. Code of the form obj.Ready.then(...) will always use whatever promise property is returned by the Ready property, so whenever some action is about to invalidate object state, a fresh promise can be created.
Closing notes
The Readiness pattern isn't specific to construction. It is easily applied to construction but it's really about ensuring that state dependencies are met. In these days of asynchronous code you need a system, and the simple declarative semantics of a promise make it straightforward to express the idea that an action should be taken ASAP, with emphasis on possible. Once you start framing things in these terms, arguments about long running methods or constructors become moot.
Deferred initialisation still has its place; as I mentioned you can combine Readiness with lazy load. But if chances are that you won't use the object, then why create it early? It might be better to create on demand. Or it might not; sometimes you can't tolerate delay between the recognition of need and fulfilment.
There's more than one way to skin a cat. When I write embedded software I create everything up front including resource pools. This makes leaks impossible and memory demands are known at compile time. But that's only a solution for a small closed problem space.
Use an asynchronous factory method instead.
class MyClass {
private mMember: Something;
constructor() {
this.mMember = await SomeFunctionAsync(); // error
}
}
Becomes:
class MyClass {
private mMember: Something;
// make private if possible; I can't in TS 1.8
constructor() {
}
public static CreateAsync = async () => {
const me = new MyClass();
me.mMember = await SomeFunctionAsync();
return me;
};
}
This will mean that you will have to await the construction of these kinds of objects, but that should already be implied by the fact that you are in the situation where you have to await something to construct them anyway.
There's another thing you can do but I suspect it's not a good idea:
// probably BAD
class MyClass {
private mMember: Something;
constructor() {
this.LoadAsync();
}
private LoadAsync = async () => {
this.mMember = await SomeFunctionAsync();
};
}
This can work and I've never had an actual problem from it before, but it seems to be dangerous to me, since your object will not actually be fully initialized when you start using it.
Another way to do it, which might be better than the first option in some ways, is to await the parts, and then construct your object after:
export class MyClass {
private constructor(
private readonly mSomething: Something,
private readonly mSomethingElse: SomethingElse
) {
}
public static CreateAsync = async () => {
const something = await SomeFunctionAsync();
const somethingElse = await SomeOtherFunctionAsync();
return new MyClass(something, somethingElse);
};
}
I've found a solution that looks like
export class SomeClass {
private initialization;
// Implement async constructor
constructor() {
this.initialization = this.init();
}
async init() {
await someAsyncCall();
}
async fooMethod() {
await this.initialization();
// ...some other stuff
}
async barMethod() {
await this.initialization();
// ...some other stuff
}
It works because Promises that powers async/await, can be resolved multiple times with the same value.
I know it's quite old but another option is to have a factory that will create the object and wait for its initialization:
// Declare the class
class A {
// Declare class constructor
constructor() {
// We didn't finish the async job yet
this.initialized = false;
// Simulates async job, it takes 5 seconds to have it done
setTimeout(() => {
this.initialized = true;
}, 5000);
}
// do something usefull here - thats a normal method
useful() {
// but only if initialization was OK
if (this.initialized) {
console.log("I am doing something useful here")
// otherwise throw an error which will be caught by the promise catch
} else {
throw new Error("I am not initialized!");
}
}
}
// factory for common, extensible class - that's the reason for the constructor parameter
// it can be more sophisticated and accept also params for constructor and pass them there
// also, the timeout is just an example, it will wait for about 10s (1000 x 10ms iterations
function factory(construct) {
// create a promise
var aPromise = new Promise(
function(resolve, reject) {
// construct the object here
var a = new construct();
// setup simple timeout
var timeout = 1000;
// called in 10ms intervals to check if the object is initialized
function waiter() {
if (a.initialized) {
// if initialized, resolve the promise
resolve(a);
} else {
// check for timeout - do another iteration after 10ms or throw exception
if (timeout > 0) {
timeout--;
setTimeout(waiter, 10);
} else {
throw new Error("Timeout!");
}
}
}
// call the waiter, it will return almost immediately
waiter();
}
);
// return promise of the object being created and initialized
return a Promise;
}
// this is some async function to create object of A class and do something with it
async function createObjectAndDoSomethingUseful() {
// try/catch to capture exceptions during async execution
try {
// create object and wait until its initialized (promise resolved)
var a = await factory(A);
// then do something usefull
a.useful();
} catch(e) {
// if class instantiation failed from whatever reason, timeout occured or useful was called before the object finished its initialization
console.error(e);
}
}
// now, perform the action we want
createObjectAndDoSomethingUsefull();
// spaghetti code is done here, but async probably still runs
Use a private constructor and a static factory method FTW. It is the best way to enforce any validation logic or data enrichment, encapsulated away from a client.
class Topic {
public static async create(id: string): Promise<Topic> {
const topic = new Topic(id);
await topic.populate();
return topic;
}
private constructor(private id: string) {
// ...
}
private async populate(): Promise<void> {
// Do something async. Access `this.id` and any other instance fields
}
}
// To instantiate a Topic
const topic = await Topic.create();
Use a factory. That's the best practice for these cases.
The problem is that is tricky to define Typescript types for the factory pattern, especially with inheritance.
Let's see how to properly implement it in Typescript.
No inheritance
If you don't need class inheritance, the pattern is this:
class Person {
constructor(public name: string) {}
static async Create(name: string): Promise<Person> {
const instance = new Person(name);
/** Your async code here! **/
return instance;
}
}
const person = await Person.Create('John');
Class inheritance
If you need to extend the class, you will run into a problem. The Create method always returns the base class.
In Typescript, you can fix this with Generic Classes.
type PersonConstructor<T = {}> = new (...args: any[]) => T;
class Person {
constructor(public name: string) {}
static async Create<T extends Person>(
this: PersonConstructor<T>,
name: string,
...args: any[]
): Promise<T> {
const instance = new this(name, ...args);
/** Your async code here! **/
return instance;
}
}
class MyPerson extends Person {
constructor(name: string, public lastName: string) {
super(name);
}
}
const myPerson = await MyPerson.Create('John', 'Snow');
Extending the factory
You can extend the Create method too.
class MyPerson extends Person {
constructor(name: string, public lastName: string) {
super(name);
}
static async Create<T extends Person>(
this: PersonConstructor<T>,
name: string,
lastName: string,
...args: any[]
): Promise<T> {
const instance = await super.Create(name, lastName, ...args);
/** Your async code here! **/
return instance as T;
}
}
const myPerson = await MyPerson.Create('John', 'Snow');
Less verbose alternative
We can reduce the code verbosity by leveraging the async code to a non-static method, which won't require a Generic Class definition when extending the Create method.
type PersonConstructor<T = {}> = new (...args: any[]) => T;
class Person {
constructor(public name: string) {}
protected async init(): Promise<void> {
/** Your async code here! **/
// this.name = await ...
}
static async Create<T extends Person>(
this: PersonConstructor<T>,
name: string,
...args: any[]
): Promise<T> {
const instance = new this(name, ...args);
await instance.init();
return instance;
}
}
class MyPerson extends Person {
constructor(name: string, public lastName: string) {
super(name);
}
override async init(): Promise<void> {
await super.init();
/** Your async code here! **/
// this.lastName = await ...
}
}
const myPerson = await MyPerson.Create('John', 'Snow');
Aren't static methods bad practice?
Yes, with one exception: factories.
Why not return a promise in the constructor?
You can do that, but many will consider your code a bad pattern, because a constructor:
Should always return the class type (Promise<Person> is not Person);
Should never run async code;
You may elect to leave the await out of the equation altogether. You can call it from the constructor if you need to. The caveat being that you need to deal with any return values in the setup/initialise function, not in the constructor.
this works for me, using angular 1.6.3.
import { module } from "angular";
import * as R from "ramda";
import cs = require("./checkListService");
export class CheckListController {
static $inject = ["$log", "$location", "ICheckListService"];
checkListId: string;
constructor(
public $log: ng.ILogService,
public $loc: ng.ILocationService,
public checkListService: cs.ICheckListService) {
this.initialise();
}
/**
* initialise the controller component.
*/
async initialise() {
try {
var list = await this.checkListService.loadCheckLists();
this.checkListId = R.head(list).id.toString();
this.$log.info(`set check list id to ${this.checkListId}`);
} catch (error) {
// deal with problems here.
}
}
}
module("app").controller("checkListController", CheckListController)
Use a setup async method that returns the instance
I had a similar problem in the following case: how to instanciate a 'Foo' class either with an instance of a 'FooSession' class or with a 'fooSessionParams' object, knowing that creating a fooSession from a fooSessionParams object is an async function?
I wanted to instanciate either by doing:
let foo = new Foo(fooSession);
or
let foo = await new Foo(fooSessionParams);
and did'nt want a factory because the two usages would have been too different. But as we know, we can not return a promise from a constructor (and the return signature is different). I solved it this way:
class Foo {
private fooSession: FooSession;
constructor(fooSession?: FooSession) {
if (fooSession) {
this.fooSession = fooSession;
}
}
async setup(fooSessionParams: FooSessionParams): Promise<Foo> {
this.fooSession = await getAFooSession(fooSessionParams);
return this;
}
}
The interesting part is where the setup async method returns the instance itself.
Then if I have a 'FooSession' instance I can use it this way:
let foo = new Foo(fooSession);
And if I have no 'FooSession' instance I can setup 'foo' in one of these ways:
let foo = await new Foo().setup(fooSessionParams);
(witch is my prefered way because it is close to what I wanted first)
or
let foo = new Foo();
await foo.setup(fooSessionParams);
As an alternative I could also add the static method:
static async getASession(fooSessionParams: FooSessionParams): FooSession {
let fooSession: FooSession = await getAFooSession(fooSessionParams);
return fooSession;
}
and instanciate this way:
let foo = new Foo(await Foo.getASession(fooSessionParams));
It is mainly a question of style…
Create holder for promise status:
class MyClass {
constructor(){
this.#fetchResolved = this.fetch()
}
#fetchResolved: Promise<void>;
fetch = async (): Promise<void> => {
return new Promise(resolve => resolve()) // save data to class property or simply add it by resolve() to #fetchResolved reference
}
isConstructorDone = async (): boolean => {
await this.#fetchResolved;
return true; // or any other data depending on constructor finish the job
}
}
To use:
const data = new MyClass();
const field = await data.isConstructorDone();
Or you can just stick to the true ASYNC model and not overcomplicate the setup. 9 out of 10 times this comes down to asynchronous versus synchronous design. For example I have a React component that needed this very same thing were I was initializing the state variables in a promise callback in the constructor. Turns out that all I needed to do to get around the null data exception was just setup an empty state object then set it in the async callback. For example here's a Firebase read with a returned promise and callback:
this._firebaseService = new FirebaseService();
this.state = {data: [], latestAuthor: '', latestComment: ''};
this._firebaseService.read("/comments")
.then((data) => {
const dataObj = data.val();
const fetchedComments = dataObj.map((e: any) => {
return {author: e.author, text: e.text}
});
this.state = {data: fetchedComments, latestAuthor: '', latestComment: ''};
});
By taking this approach my code maintains it's AJAX behavior without compromising the component with a null exception because the state is setup with defaults (empty object and empty strings) prior to the callback. The user may see an empty list for a second but then it's quickly populated. Better yet would be to apply a spinner while the data loads up. Oftentimes I hear of individuals suggesting overly complicated work arounds as is the case in this post but the original flow should be re-examined.

ReactJS Fixed-Data-Table and Async JSON for DataListStore

I am trying to learn ReactJS with ES6 along with setting up an instance of Fixed-Data-Table. I'm using the ObjectDataExample example from the github repo, but instead of the faker() values fed to the DataListStore, I want to use a DataListStore that gets its cache from a remote JSON resource. This is how I have defined my DataListStore:
class MyDataListStore {
constructor(/* url string */ url) {
this.url = url || 'http://localhost:8080/default-json';
this._cache = [];
this.pageSize = 1;
this.size = 0;
this.getRemoteData(url);
}
getRemoteData() {
/**
* Fetch remote JSON to be used in the store.
*/
var that = this;
fetch(this.url).then(function(response) {
return response.json();
}).then(function(j) {
console.log(j);
//this.pageSize = j["pages"];
that.size = j["total"];
that._cache = j["table"];
if (that._cache) {
// do something here?
}
});
}
getObjectAt(/*number*/ index) /*?object*/ {
if (index < 0 || index > this.size){
return undefined;
}
if (this._cache[index] === undefined) {
//this._cache[index] = this.createFakeRowObjectData(index);
}
return this._cache[index];
}
getSize() {
return this.size;
}
}
module.exports = MyDataListStore;
As you can see I'm following the FakeObjectDataListStore provided with the example from fixed-data-table more or less. The JSON is fetched properly, the _cache is populated with an array of objects, and when you output getSize once getRemoteData has executed, you do get the size of the _cache. However, I haven't figured out how my fixed-data-table Table component should be updated once the data has been fetched. Currently the Table is rendered but is simple blank with no rows.
class ObjectDataExample extends React.Component {
constructor(props) {
super(props);
this.state = {
dataList: new MyDataListStore()
};
}
render() {
var {dataList} = this.state;
return <Table
rowHeight={70} rowsCount={dataList.getSize()} width={1170} height={500} headerHeight={30}>
<Column
header={<Cell>ID</Cell>}
cell={<TextCell data={dataList} col="id" />}
width={50}
fixed={true}
/>
<Column
header={<Cell>Email</Cell>}
cell={<TextCell data={dataList} col="email" />}
width={300}
fixed={true}
/>
</Table>
}
}
module.exports = ObjectDataExample;
I think the main issue is that I don't have any code meant to populate the table once MyDataListStore is populated with the data from the async call. However, I can't find any help from the examples given in the Fixed-Data-Table github repo or the docs. Any idea how to get this done? I assume I need to set up some sort of event listener, but I'm not sure where/how to do this, as I'm still new to both ReactJS and Fixed-Data-Table.
Edit: I should also add that when the page loads, I get the following error:
Uncaught TypeError: Cannot read property 'id' of undefined
once I set the initial this.size to more than 0. So of course the table doesn't have the available data when it's first loading.
Edit 2: After looking into this further, it looks like if I run the fetch in componentDidMount of my ObjectDataExample and use this.setState(); to reset the dataList object, then I get the table updated. However, this looks a little messy and I'd assume there's a better way to do this directly from my MyDataListStore object.
Thanks,
One design issue with the current implementation of MyDataListStore is that it does not provide a way to notify the caller when the data has been loaded.
One possible way you might do this is to implement some sort of factory function (in the example below, I'm pretending that one exists called MyDataListStore.of) that returns a Promise that eventually resolves the MyDataListStore instance once the data loads:
// In the ObjectData component constructor, we call the MyDataListStore
// factory function and once it resolves, we assign it to our
// state. This will cause our component to re-render.
constructor() {
MyDataListStore.of(myDataListStoreUrl).then(store => {
this.setState({ dataList: store });
});
}
Now, once the data in the data list store resolves, our template (specified in your render function) will render correctly.
The DataListStore.of function we used earlier might look something like this:
class MyDataListStore {
static of(url) {
const dataListStore = new MyDataListStore(url);
return dataListStore.getRemoteData().then(() => return dataListStore);
}
/* ... other MyDataListStore properties/methods ... */
}
And finally we need to update the getRemoteData to return a promise. This is what will allow any clients of our MyDataListStore class to be notified that the data has loaded:
getRemoteData() {
/**
* Fetch remote JSON to be used in the store.
*/
var that = this;
// Return the chained promise! This promise will resolve
// after our last callback is called.
return fetch(this.url).then(function(response) {
return response.json();
}).then(function(j) {
console.log(j);
//this.pageSize = j["pages"];
that.size = j["total"];
that._cache = j["table"];
if (that._cache) {
// do something here?
}
});
}