Scope issue in provider using cordova File plugin

Hey everyone, precursor apology if this belongs in another forum, I’m so new to many parts of this system, so just be blunt if so! :slight_smile:

All I’m trying to do is when iterating over the files, I’m trying to put them in the ‘meditations’ member variable, but for some reason, (a closure, or threading issue is my guess?) when it hits the this.meditations.push() command, it bombs and says: There was an error: undefined is not an object (evaluating ‘this.meditations’) My strengths lie in other languages, so I don’t even know where to start to get this data into a class member variable. Can anyone point me at how to make this happen? (I am running this in a simulator, it sees the cordova variable, that is all fine.) What am I missing? Thanks so much for any help!

Here’s the relevant code:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Platform } from 'ionic-angular';
import { File } from 'ionic-native';
import 'rxjs/add/operator/map';

declare var cordova: any;

/*
  Generated class for the MeditationData provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class MeditationData {

  meditations : String[];

  constructor(private http: Http, public platform: Platform) {

    

    this.platform.ready().then((readySource) => {
      console.log('Platform ready from ' + readySource);
      console.log('cordova.file.applicationDirectory: ' + cordova.file.applicationDirectory);

      File.listDir(cordova.file.applicationDirectory, 'www/build/meditations/').then(
        (files) => {

          files.forEach(function(file, index) {
            console.log("Found file: " + file.name + " index: " + index);
            this.meditations.push(file.name);

          });
        }
      ).catch(
        (err) => {
          console.log("There was an error: " + err.message);
          // do something
        }
      );

      console.log("Dumping meditations");

      this.meditations.forEach(function(filename) {
        console.log("Filename: " + filename);
      });
      
    });
  }

  public getMeditations() {
    return this.meditations;
  }

}

So in typescript it’s normally best to use fat arrows (=>) rather than using function, especially if you’re going to refer to this. (Which you’re certainly already partially doing!) Why is this the case? The tl;dr is that context is weird in Javascript and so this could refer to any number of things at any given point, so using fat arrows automagically retains the correct context (thus it retains the correct this).

So! What does this mean for an actual solution? It simply means taking this:

          files.forEach(function(file, index) {
            console.log("Found file: " + file.name + " index: " + index);
            this.meditations.push(file.name);

          });

and changing it to:

          files.forEach((file, index) => {
            console.log("Found file: " + file.name + " index: " + index);
            this.meditations.push(file.name);

          });

Thanks so much for the response! That was helpful, I was able to look up a bunch of things I didn’t understand, much appreciated! It’s still giving me the same problem for some reason though! It really seems like it should be working, from what I read, it seems using the fat arrow should push “this” forward each time right? Could it be from being nested like that? But even that seems like it should be ok, because it’s just bringing in the same “this” each time in? Thanks again!!!

This was the error output:

4     503861   log      Dumping meditations
5     503867   group    EXCEPTION: Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating '_this.meditations.forEach')
6     503868   error    EXCEPTION: Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating '_this.meditations.forEach')
7     503869   error    STACKTRACE:
8     503870   error    resolvePromise@http://192.168.1.111:8100/build/js/zone.js:538:41
http://192.168.1.111:8100/build/js/zone.js:574:32
invokeTask@http://192.168.1.111:8100/build/js/zone.js:356:43
onInvokeTask@http://192.168.1.111:8100/build/js/app.bundle.js:37183:51
invokeTask@http://192.168.1.111:8100/build/js/zone.js:355:55
runTask@http://192.168.1.111:8100/build/js/zone.js:256:58
drainMicroTaskQueue@http://192.168.1.111:8100/build/js/zone.js:474:43
invoke@http://192.168.1.111:8100/build/js/zone.js:426:41
9     503870   groupEnd 
10    503872   error    Unhandled Promise rejection:, undefined is not an object (evaluating '_this.meditations.forEach'), ; Zone:, angular, ; Task:, Promise.then, ; Value:, [object Object]
11    503934   log      Found file: 7_minute_meditation index: 0
12    503935   log      There was an error: undefined is not an object (evaluating '_this.meditations.push')

And this is the code after I added the fix:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Platform } from 'ionic-angular';
import { File } from 'ionic-native';
import 'rxjs/add/operator/map';

declare var cordova: any;

@Injectable()
export class MeditationData {

  meditations : String[];

  constructor(private http: Http, public platform: Platform) {

    this.platform.ready().then((readySource) => {
      console.log('Platform ready from ' + readySource);
      console.log('cordova.file.applicationDirectory: ' + cordova.file.applicationDirectory);

      File.listDir(cordova.file.applicationDirectory, 'www/build/meditations/').then(
        (files) => {

          files.forEach((file, index) => {
            console.log("Found file: " + file.name + " index: " + index);
            this.meditations.push(file.name);
          });
          
        }
      ).catch(
        (err) => {
          console.log("There was an error: " + err.message);
          // do something
        }
      );

      console.log("Dumping meditations");

      this.meditations.forEach(function(filename) {
        console.log("Filename: " + filename);
      });
      
    });
  }

}

Oh! It looks like one problem might be with you dumping the meditations. You’re iterating over the them, but it’s before you’ve added all of them. (It’s an async problem)

So you can change the block inside of platform.ready to this:

            File.listDir(cordova.file.applicationDirectory, 'www/build/meditations/').then(
            (files) => {

                files.forEach((file, index) => {
                    console.log("Found file: " + file.name + " index: " + index);
                    this.meditations.push(file.name);
                });

            }
            ).catch(
                (err) => {
                    console.log("There was an error: " + err.message);
                    // do something
                }
            )
            .then(_ => { 
                //Adding this section right here, as this will run once all the meditations have been added
                console.log("Dumping meditations");

                this.meditations.forEach(function(filename) {
                    console.log("Filename: " + filename);
                });
            });

Do you try to run this code on desktop? Cordova plugins does not run on desktop browser.
You should use device to test this code.

No, I’'m using it on iOS simulator and real device. :slight_smile: