Angular: Cloudinary error { "message": "cloud_name is disabled" }

Viewed 1162

I'm trying to use Cloudinary image upload with Angular. I've followed their docs to install the sdk, the image uploader. And I've followed some sample projects for the component and the html.

Everything works great within my project but when I try to upload an image I keep getting the error:

Upload completed with status code 401
error { "message": "cloud_name is disabled" }

Here is my component.ts:

import { Component, OnInit, Input, NgZone } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FileUploader, FileUploaderOptions, ParsedResponseHeaders, FileUploadModule } from 'ng2-file-upload';
import { Cloudinary } from '@cloudinary/angular-5.x';

// https://www.youtube.com/watch?v=YkvqLNcJz3Y

@Component({
  selector: 'app-cloudinary',
  templateUrl: './cloudinary.component.html',
  styleUrls: ['./cloudinary.component.scss']
})
export class CloudinaryComponent implements OnInit {

  @Input()
  responses: Array<any>;

  public hasBaseDropZoneOver: boolean = false;
  public uploader: FileUploader;
  private title: string;

  constructor(
    private cloudinary: Cloudinary,
    private zone: NgZone,
    private http: HttpClient
  ) {
    this.responses = [];
    this.title = '';
  }

  ngOnInit(): void {
    // Create the file uploader, wire it to upload to your account
    const uploaderOptions: FileUploaderOptions = {
      url: `https://api.cloudinary.com/v1_1/${this.cloudinary.config().CLOUD_NAME}/upload`,
      // Upload files automatically upon addition to upload queue
      autoUpload: true,
      // Use xhrTransport in favor of iframeTransport
      isHTML5: true,
      // Calculate progress independently for each uploaded file
      removeAfterUpload: true,
      // XHR request headers
      headers: [
        {
          name: 'X-Requested-With',
          value: 'XMLHttpRequest'
        }
      ]
    };
    this.uploader = new FileUploader(uploaderOptions);

    this.uploader.onBuildItemForm = (fileItem: any, form: FormData): any => {
      // Add Cloudinary's unsigned upload preset to the upload form
      form.append('upload_preset', this.cloudinary.config().upload_preset);
      // Add built-in and custom tags for displaying the uploaded photo in the list
      let tags = 'myphotoalbum';
      if (this.title) {
        form.append('context', `photo=${this.title}`);
        tags = `myphotoalbum,${this.title}`;
      }
      // Upload to a custom folder
      // Note that by default, when uploading via the API, folders are not automatically created in your Media Library.
      // In order to automatically create the folders based on the API requests,
      // please go to your account upload settings and set the 'Auto-create folders' option to enabled.
      form.append('folder', 'angular_sample');
      // Add custom tags
      form.append('tags', tags);
      // Add file to upload
      form.append('file', fileItem);

      // Use default "withCredentials" value for CORS requests
      fileItem.withCredentials = false;
      return { fileItem, form };
    };

    // Insert or update an entry in the responses array
    const upsertResponse = fileItem => {

      // Run the update in a custom zone since for some reason change detection isn't performed
      // as part of the XHR request to upload the files.
      // Running in a custom zone forces change detection
      this.zone.run(() => {
        // Update an existing entry if it's upload hasn't completed yet

        // Find the id of an existing item
        const existingId = this.responses.reduce((prev, current, index) => {
          if (current.file.name === fileItem.file.name && !current.status) {
            return index;
          }
          return prev;
        }, -1);
        if (existingId > -1) {
          // Update existing item with new data
          this.responses[existingId] = Object.assign(this.responses[existingId], fileItem);
        } else {
          // Create new response
          this.responses.push(fileItem);
        }
      });
    };

    // Update model on completion of uploading a file
    this.uploader.onCompleteItem = (item: any, response: string, status: number, headers: ParsedResponseHeaders) =>
      upsertResponse(
        {
          file: item.file,
          status,
          data: JSON.parse(response)
        }
      );

    // Update model on upload progress event
    this.uploader.onProgressItem = (fileItem: any, progress: any) =>
      upsertResponse(
        {
          file: fileItem.file,
          progress,
          data: {}
        }
      );
  }

  updateTitle(value: string) {
    this.title = value;
  }

  // Delete an uploaded image
  // Requires setting "Return delete token" to "Yes" in your upload preset configuration
  // See also https://support.cloudinary.com/hc/en-us/articles/202521132-How-to-delete-an-image-from-the-client-side-
  deleteImage = function (data: any, index: number) {
    const url = `https://api.cloudinary.com/v1_1/${this.cloudinary.config().CLOUD_NAME}/delete_by_token`;
    const headers = new Headers({ 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' });
    const options = { headers: headers };
    const body = {
      token: data.delete_token
    };
    this.http.post(url, body, options).subscribe(response => {
      console.log(`Deleted image - ${data.public_id} ${response.result}`);
      // Remove deleted item for responses
      this.responses.splice(index, 1);
    });
  };

  fileOverBase(e: any): void {
    this.hasBaseDropZoneOver = e;
  }

  getFileProperties(fileProperties: any) {
    // Transforms Javascript Object to an iterable to be used by *ngFor
    if (!fileProperties) {
      return null;
    }
    return Object.keys(fileProperties)
      .map((key) => ({ 'key': key, 'value': fileProperties[key] }));
  }
}

Here is my component.html:

<h1>Image Upload to Cloudinary TESTING</h1>

<div id="direct_upload" ng2FileDrop [uploader]="uploader" (fileOver)="fileOverBase($event)" [ngClass]="{'nv-file-over': hasBaseDropZoneOver}">
    <h1>New Photo</h1>
    <h2>Direct upload from the browser with Angular File Upload</h2>
    <p>You can also drag and drop an image file into the dashed area.</p>
    <form>
        <div class="form_line">
            <label path="title">Title:</label>
            <div class="form_controls">
                <input type="text" class="form-control" #title placeholder="Title" (keyup.enter)="updateTitle(title.value)" (blur)="updateTitle(title.value)"
                />
            </div>
        </div>
        <div class="form_line">
            <label>Image:</label>
            <div class="form_controls">
                <div class="upload_button_holder">
                    <label class="upload_button" for="fileupload">Upload</label>
                    <!-- onChange hanlder resets the input value to get the change event when uploading the same file consecutively -->
                    <input type="file" id="fileupload" #fileInput ng2FileSelect [style.display]="'none'" [uploader]="uploader" (change)="fileInput.value=''"
                        multiple />
                </div>

            </div>
        </div>
    </form> 
    <h2>Status</h2>
    <div class="file" *ngFor="let response of responses; let i = index">
        <h3>{{response.file.name}}</h3>
    <button class="delete-image" *ngIf="!!response.data.delete_token" (click)="deleteImage(response.data, i)">Delete image</button>
        <div class="status">
            Uploading... {{response.progress}}%
            <div *ngIf="!response.status">In progress</div>
            <div class="status-code" *ngIf="response.status">Upload completed with status code {{response.status}}</div>
        </div>
        <div class="progress-bar">
            <div class="progress" role="progressbar" [style.width.%]="response.progress"></div>
        </div>
        <div class="form_line">
            <div class="form_controls">
                <div class="preview">
                    <!-- Consider using https://github.com/valor-software/ng2-file-upload/issues/461 for image preview -->
                </div>
            </div>
        </div>
        <div class="info">
            <table>
                <tr *ngFor="let property of getFileProperties(response.data)">
                    <td> {{ property.key }} </td>
                    <td> {{ property.value | json}} </td>
                </tr>
            </table>
        </div>
    </div>

</div>

And in my app.module.ts I did my imports:

...
import { CloudinaryModule, CloudinaryConfiguration } from '@cloudinary/angular-5.x';
import { Cloudinary } from 'cloudinary-core';
import { FileUploadModule } from "ng2-file-upload";

@NgModule({
  declarations: [
    ...
    CloudinaryComponent
  ],
  imports: [
    ...
    // Cloudinary import
    CloudinaryModule.forRoot({Cloudinary}, { cloud_name: 'CLOUDN_NAME' } as CloudinaryConfiguration),
    FileUploadModule
  ],
  providers: [
    ...
  ],
  bootstrap: [AppComponent]
})


export class AppModule { }

So I really don't understand why it's not getting the authorization, but something that bothers me as well is that within Cloudinary docs with Angular I don't see where I should put my API Key and my API Secret.

And nowhere in my project I have file like systemjs.config.js rollup-config.js.

If anyone could help me out it would be awesome!

Thanks

1 Answers

Well in your example you need to change your cloud URL.

You have https://api.cloudinary.com/v1_1/${this.cloudinary.config().CLOUD_NAME}/upload but you need https://api.cloudinary.com/v1_1/CLOUD_NAME/upload.

Your https://api.cloudinary.com/v1_1/${this.cloudinary.config().CLOUD_NAME}/delete_by_token also needs to be changed to https://api.cloudinary.com/v1_1/CLOUD_NAME/delete_by_token

And your upload_preset you need to change it from form.append('upload_preset', this.cloudinary.config().upload_preset) to form.append('upload_preset', 'PRESET_NAME')

So you component should look like this:

...
  ngOnInit(): void {
    // Create the file uploader, wire it to upload to your account
    const uploaderOptions: FileUploaderOptions = {
      url: `https://api.cloudinary.com/v1_1/${this.cloudinary.config().CLOUD_NAME}/upload`,
      // Upload files automatically upon addition to upload queue
      autoUpload: true,
      // Use xhrTransport in favor of iframeTransport
      isHTML5: true,
      // Calculate progress independently for each uploaded file
      removeAfterUpload: true,
      // XHR request headers
      headers: [
        {
          name: 'X-Requested-With',
          value: 'XMLHttpRequest'
        }
      ]
    };
    this.uploader = new FileUploader(uploaderOptions);

    this.uploader.onBuildItemForm = (fileItem: any, form: FormData): any => {
      // Add Cloudinary's unsigned upload preset to the upload form
      form.append('upload_preset', 'PRESET_NAME');
      // Add built-in and custom tags for displaying the uploaded photo in the list
      let tags = 'myphotoalbum';
      if (this.title) {
        form.append('context', `photo=${this.title}`);
        tags = `myphotoalbum,${this.title}`;
      }
      // Upload to a custom folder
      // Note that by default, when uploading via the API, folders are not automatically created in your Media Library.
      // In order to automatically create the folders based on the API requests,
      // please go to your account upload settings and set the 'Auto-create folders' option to enabled.
      form.append('folder', 'angular_sample');
      // Add custom tags
      form.append('tags', tags);
      // Add file to upload
      form.append('file', fileItem);

      // Use default "withCredentials" value for CORS requests
      fileItem.withCredentials = false;
      return { fileItem, form };
    };
...
  // Delete an uploaded image
  // Requires setting "Return delete token" to "Yes" in your upload preset configuration
  // See also https://support.cloudinary.com/hc/en-us/articles/202521132-How-to-delete-an-image-from-the-client-side-
  deleteImage = function (data: any, index: number) {
    const url = `https://api.cloudinary.com/v1_1/CLOUD_NAME/delete_by_token`;
    const headers = new Headers({ 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' });
    const options = { headers: headers };
    const body = {
      token: data.delete_token
    };
...

And for the API key, if you are uploading to a signed upload_preset you can your keys in your component like this, as shown in this sample project angular:

...
 ngOnInit(): void {
   
    console.log("initialized");
    (window as any).cloudinary.createMediaLibrary(
      {
        cloud_name: "<cloud name>",
        api_key: "<api key>",
        button_class: "myBtn",
        username: "<user email>",
        button_caption: "Select Image or Video"
      },
...
Related