2

I have a material design table and I wrote custom functions to load the data and extract the objects from the JSON array object.

I have the following code:

  public getDocumentList() {
    return this.http.get(this.getDocumentUrl, this.httpOptions)
    .subscribe(
      documentResponse => {
        for (let i = 0; i > Object.keys(documentResponse).length; i++){
          console.log(Object.keys(documentResponse));
          console.log("documentResponse:");
          console.log(documentResponse);
          console.log(of(documentResponse[i]).pipe(pluck('soap:Envelope', 'soap:Body', 'ns2:getDocumentsResponse', 'return')));
          this.documentList$ = of(documentResponse[i]).pipe(pluck('soap:Envelope', 'soap:Body', 'ns2:getDocumentsResponse', 'return'));
        this.documentList$.subscribe(x => this.documentListArray.push(x));
        console.log("Doklist", this.documentListArray)
      }
      this.setDokumentStatus();
       },
       error => {
        alert('Following error happened:' + ' ' + error['statusText']);
        console.log('There was an error: ', error);
      });
  }

The following public function above fills documentListArray with the required objects...

But this semi-random error gets thrown:

ERROR Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading 'length')
TypeError: Cannot read properties of undefined (reading 'length')
    at WorkDocumentComponent.<anonymous> (work-document.component.ts:139:6

The last line of this code is where the error happens (before the bracket obviously):

async FillElementDataArray() {
    ELEMENT_DATA.length = 0;
    this.dataSource.connect().next(ELEMENT_DATA);

    let add_WorkDocument = {} as WorkDocument;
    console.log(this.DataService.documentListArray);
    let docsForMetadata;

    // this below is undefined yo
    for (let i = 0; i < this.DataService.documentListArray[0].length; i++)
    {

...which is VERY strange.

Is this an async and sync function issue?

  • How do I fix this?
  • Why is it still undefined and I cannot console.log it? EDIT: I can console.log it IF I put the console.log before the log loop.

P.S. my documentResponse looks like this:

[
{
"soap:Envelope": {
"soap:Body": {
                "ns2:getDocumentMetaDataResponse": {
                    "return": {
                        "items": [
{
"key": "blah",
"values": "blablabla"
and so on...
}
]
Munchkin
  • 857
  • 5
  • 24
  • 51
  • the way you loop thru the array is just weird. First documentResponse is an array, so use .length directly, no need Object.keys, secondary, no need to convert to Observable with of and the subscribe just to push it into another array, documentListArray – Jimmy Nov 29 '22 at 18:42
  • `documentResponse` appears to be an object for some reason... – Munchkin Nov 30 '22 at 09:18
  • I thought the `of` and `subscribe` is mandatory for plucking – Munchkin Nov 30 '22 at 10:13
  • Maybe what's happening (along other things) is that `FillElementDataArray` is getting called before `getDocumentList` has finished loading? This would explain what you mention that "documentListArray seems to still be undefined", because it was never set on the first place. RxJS is designed to solve these issues, but you need a bit of a refactor. I'll try putting in an answer that could help. – olivarra1 Dec 02 '22 at 10:45
  • @olivarra1 So it is an sync/async issue after all then! That might very well be the case I just don't know how to debug it. I even tried `console.log`ging the Observable itself, but that doesn't really help. – Munchkin Dec 02 '22 at 10:46

2 Answers2

3

What you need is called optional chaining and nothing to do with any of either promise or subscription.

If you take a look at the line that error claims and read the error itself, this.DataService.documentListArray[0] value has no data at some point so that, reading length property for it returns an error.

Moreover the error is common when tried to read a property of an undefined value, hence the solution is as easy as below;

for (let i = 0; i < this.DataService.documentListArray[0]?.length; i++)
{

}
Erhan Yaşar
  • 847
  • 1
  • 9
  • 25
  • Don't I need to iterate thru the Array though?.. – Munchkin Dec 02 '22 at 08:06
  • I mean what's the point of the loop otherwise?.. – Munchkin Dec 02 '22 at 08:19
  • It’s just a protection for the case that when this value returns undefined (as an initial state or whatever the case). Via optional chaining, it’s just a safety check before it’s execution. Incase there’s no value/data as expected, then the line will not be executed so that it doesn’t return an error. Otherwise it makes no change on execution @Munchkin. – Erhan Yaşar Dec 02 '22 at 08:48
  • Ok, but the documentListArray seems to still be undefined in the scope when I `console.log` it – Munchkin Dec 02 '22 at 10:37
  • Well that means there’s another problem (there might be even more than one) with your implementation since there isn’t any minimum working example shared by you to let us edit all. Hence my answer above is for the error shared above as it should be expected to be fixed in any case. Feel free to ask another question or editing this one to get a solution for that as well. – Erhan Yaşar Dec 02 '22 at 11:01
  • I don't want to sound mean, but I'll quote the bounty description: "That would mean that the frontend code here has to fully fill the material design table with the data using the code." – Munchkin Dec 02 '22 at 11:09
  • 1
    It doesn’t sound mean. I tried to provide an answer to the error shown since it takes fixing more than couple errors. It’s not fault of us either but you should update your question with the provided answers and so for the errors you described within the question. When you don’t provide minimum working example here, no one can predict more. Besides you don’t need to award any bounty if your problems haven’t resolved yet but you need to provide each detail in that case to get an answer at least. – Erhan Yaşar Dec 02 '22 at 11:20
1

I have the suspicion that by the time FillElementDataArray is called, getDocumentList hasn't loaded yet.

RxJS is designed to work with asynchronous flows, but you need to structure your code in a way that you can compose the data down.

public getDocumentList() {
  this.documentList$ = this.http.get(this.getDocumentUrl, this.httpOptions).pipe(
    map(documentResponse => {
      // Assuming documentResponse is an array
      return documentResponse.map(
        // RxJS deprecates pluck in favour of just optional chaining
        x => x?.['soap:Envelope']?.['soap:Body']?.['ns2:getDocumentsResponse']?.['return']
      )
    }),
    // Not sure why this call is needed
    tap(() => this.setDokumentStatus())
  )
}

Then on the async function you can consume this observable with lastValueFrom, which gives you a promise with the last value the observable emitted before completing (in this case it will emit one value and complete)

async FillElementDataArray() {
  ELEMENT_DATA.length = 0;
  this.dataSource.connect().next(ELEMENT_DATA);

  let add_WorkDocument = {} as WorkDocument;
  const documentListArray = await lastValueFrom(this.DataService.documentList$);
  let docsForMetadata;

  // this below is undefined yo
  for (let i = 0; i < documentListArray.length; i++)
  {

Not sure if this answer solves your problem, because this needs more... ideally getDocumentList() already returns the observable. And also something to keep in mind is that observables are lazy... if you'd like to have getDocumentList start loading before someone subscribes you'll need to shareReplay that observable and create a subscription (which is also not elegant)

olivarra1
  • 3,269
  • 3
  • 23
  • 34
  • `setDokumentStatus()` is used to push `this.DokumentsLoaded.next({});` which signals to the app that the data has been successfully loaded. Before it was running in a `setTimeout` "loop" but now it uses observables. The code is very alpha/betalike though.. I'll try out your code suggestions and try whether it works now. – Munchkin Dec 02 '22 at 11:07
  • Well... documentResponse is apparently an object for some reason even though the JSON doesn't look like one and that throws an error :( – Munchkin Dec 02 '22 at 11:11
  • Weirdly enough I managed to solve the map error with defining the index of `[0]`, but I'm not sure that's correct – Munchkin Dec 02 '22 at 12:41
  • Also, when I try to import `lastValueFrom` from rxjs it says that function is not exported... Since what version of RxJS is there this operator/function? – Munchkin Dec 02 '22 at 12:46
  • My version should be 6.6.7 – Munchkin Dec 02 '22 at 12:53
  • I updated the RxJS version and now it complains at the `documentListArray.length` part that `documentListArray` is unknown and `.length` is not a known property of `unknown`. Any idea on how do I fix this? I defined it as type any and now it doesn't print any errors but nothing happens... – Munchkin Dec 12 '22 at 06:53
  • what's the type of `this.DataService.documentList$`? Or the observable you are passing to `lastValueFrom`? – olivarra1 Dec 12 '22 at 14:23
  • To quote my code: `public documentList$: any;`, my recent try was: `await lastValueFrom(of(this.DataService.documentList$))` – Munchkin Dec 12 '22 at 14:30