2

I'm trying to add an option to add an image in my Angular 2 App and wanted to use Filestack (formerly filepicker.io) to store the images. So I included these script tags in my index html file above </body>, as Filestack suggested (and put my API key in) and added the <input> field in my component html which displays the form to add a new recipe:

in index.html:

<script src="https://static.filestackapi.com/v3/filestack-0.5.0.js"></script>
<script>
    var url = '';
    var client = filestack.init('myApiKey');
    function showPicker() {
        client.pick({
            maxFiles: 1
        }).then(function(result) {
            url = JSON.stringify(result.filesUploaded[0].url)
        });
    }
</script>

in recipe-form.component.html:

<input type="button" value="Upload" onclick="showPicker()" />

Now that works perfectly fine, it uploads the image and if I add console.log(url) it also shows the url of the image. However, there seems to be no way to get that variable into the RecipeFormComponent where I want to add the url to the object I'm creating there. How could I do that?

I have found a lot of stuff about how to use Filestack with AngularJS, but not how to do this in Angular 2...

Is there anything you know of that could help me?

Georg
  • 221
  • 1
  • 2
  • 10

1 Answers1

4

Remove everything you have shown for index.html except for the script tag to load the API.

<script src="//static.filestackapi.com/v3/filestack-0.5.0.js"></script>

Then alter your component to incorporate the showPicker functionality

recipe-form.component.ts

declare const filestack: {
  init(apiKey: string): {
    pick({ maxFiles }: { maxFiles: number }):
      Promise<{ filesUploaded: { url: string }[] }> 
  }
};

@Component({
  // boilerplate and ceremony
})
export class RecipeFormComponent {
  uploadedFileUrls: string[] = [];

  async showPicker() {
    const client = filestack.init('myApiKey');
    const result = await client.pick({ maxFiles: 1 });
    const url = result.filesUploaded[0].url;
    this.uploadedFileUrls.push(url);
  }
}

To improve maintainability and testability, you should move all of the code with that accesses the filestack global into a dedicated service or services.

For example we could write a service like

// file-upload.service.ts
declare const filestack: {

  init(apiKey: string): {
    pick: (options: {maxFiles: number}) => Promise<{filesUploaded: {url: string}[]}>
  }
};

const client = filestack.init('myApiKey');

export default class {
  async uploadOne() {
    const result = await client.pick({ maxFiles: 1 });
    return {urls: result.filesUploaded.map(uploaded => uploaded.url)};
  }
}

we can consume it from components by using the service which wraps the API and provides the results that matter to our application

import FileUploadService from 'app/services/file-upload.service';

@Component({
  // boilerplate and ceremony
})
export class RecipeFormComponent {
  constructor(readonly fileUploadService: FileUploadService) {}

  uploadedFileUrls: string[] = [];

  async showPicker() {
    const {urls: [url]} = await this.fileUploadService.uploadOne();

    this.uploadedFileUrls.push(url);
  }
}

Additionally, if you're using a module loader like SystemJS, you would do well to remove the script tag itself, mapping and hiding its global nature via the loader.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • Umm, if I paste that into **recipe-form.component.ts**, he couldn't find the name **client** and even if I try to intialize the client with `filestack.init()`, he doesn't find the name **filestack**... Do I need to import something here? – Georg Apr 18 '17 at 10:04
  • So I missed a line – Aluan Haddad Apr 18 '17 at 11:05
  • @Georg take a look at the revised answer – Aluan Haddad Apr 18 '17 at 12:44
  • As I said in my comment before, now there's the problem that `filestack` can't be found.. :/ – Georg Apr 18 '17 at 13:28
  • @Georg added a type declaration for the API – Aluan Haddad Apr 18 '17 at 13:52
  • Yes it was a TypeScript error which doesn't come up anymore. However, nothing's happening when I click on that button. I've also tried ``, but then I get an error in the console saying _ReferenceError: filestack is not defined_.. – Georg Apr 18 '17 at 14:08
  • Did you include the script tag in your index.html? – Aluan Haddad Apr 18 '17 at 15:06
  • 1
    Oh, I'm so sorry. I got confused with all the branches I had, because I worked on another part of the application and must have checked out when the script tag was already deleted. Now it works perfectly, thanks a lot Aluan! :) – Georg Apr 18 '17 at 15:47
  • One more simple question if you mind: the url that comes back there seems to be a String which already has **""** around it, so if I assign that to a variable, I have a String of a String. Do you know how I can fix that quickly? – Georg Apr 18 '17 at 16:16
  • Gotcha, I figured you wanted that as you had it in the OP, so I left the `JSON.stringify`. I will update my answer. – Aluan Haddad Apr 18 '17 at 16:19
  • @AluanHaddad I've tried your code but get "Uncaught ReferenceError: showPicker is not defined" even though it is. Any idea why that might be? Anything to do with it not seeing it because of the 'async' keyword? – rmcsharry May 21 '17 at 17:06
  • Make sure your typescript is up to date – Aluan Haddad May 21 '17 at 18:33
  • @AluanHaddad, How is it possible to make a kind of service for `async showPicker()`? so we could use it in different components. – Kavoos Dec 08 '17 at 13:50
  • 1
    @Kavoos it's incredibly easy. I'm sure you can figure it out, but just move the method into another class, and have it return results instead of pushing them into an array. Problem solved – Aluan Haddad Dec 08 '17 at 13:54
  • @Kavoos I'll try to update the answer later to include this but I'm sure you can figure it out – Aluan Haddad Dec 08 '17 at 13:55
  • @AluanHaddad,Thanks, It works, as you already said, I should put the method in another class, return result and in my component I should call that method and use something like `.then(result => for(...) { ... }` since we are working with Promise. – Kavoos Dec 11 '17 at 08:33
  • @Kavoos that is why I am using an `async` function. This enables the `await` sugar that allows `Promise`s to be interspersed with normal control flow constructs like loops and try blocks. In my example, I have `const result = await client.pick(...);` – Aluan Haddad Dec 11 '17 at 10:12