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