See my answer for detailed implementation of CkEditor5 in Angular with image upload in ASP.NET Core
You can also find the article below, it is like VS code format.
Possible Error :
Uncaught (in promise): TypeError: Cannot read property 'data-ck-expando' of undefined in CKEditor
Solution for the above error:
remove all the code in polyfill.ts and add below code
import 'zone.js/dist/zone.js'; // Included with Angular CLI.
(window as any).__Zone_disable_toString = true;
install below packages
npm install --save @ckeditor/ckeditor5-angular
Install one of the official editor builds or create a custom one.
Assuming that you picked @ckeditor/ckeditor5-build-classic:
npm install --save @ckeditor/ckeditor5-build-classic
Now, add CKEditorModule to your application module imports:
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
@NgModule( {
imports: [
CKEditorModule,
// ... ],
// ... } )
ckeditorEx.html:
<ckeditor [config]="editorConfig" [editor]="Editor" [(ngModel)]="description"
#desc="ngModel" spellcheck="false" required minlength="50" name="editor5" (ready)="onReady($event)">
ckeditorEx.ts
look for this url and point to your api url xhr.open('POST', 'https://www.example.com/api/articles/CkEditorUploads', true);
import { Component, OnInit } from '@angular/core';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
@Component({
selector: 'app-ckeditorEx',
templateUrl: './ckeditorEx.component.html',
styleUrls: ['./ckeditorEx.component.css']
})
export class ckeditorExComponent implements OnInit {
public Editor = ClassicEditor;
editorConfig = {
placeholder: 'Type here..',
};
description: any;
constructor() {
}
ngOnInit() {
}
onReady($event) {
$event.plugins.get('FileRepository').createUploadAdapter = (loader) => {
return new MyUploadAdapter(loader);
};
}
//ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
class MyUploadAdapter {
xhr: any;
loader: any;
constructor(loader) {
// The file loader instance to use during the upload.
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file
.then(file => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
//Replace below url with your API url
xhr.open('POST', 'https://www.example.com/api/articles/CkEditorUploads', true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve, reject, file) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: response.url
});
});
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener('progress', evt => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
// Send the request.
this.xhr.send(data);
}
}
ASP.NET Web API
Controller Constructor:
// Import host in your controller cconstructor like below
private readonly IHostingEnvironment host;
public ExampleController(
..........................
IHostingEnvironment host)
{
this.host = host;
}
Controller Method:
[AllowAnonymous]
[HttpPost("CkEditorUploads2")]
// Don't change "IFormFile upload" text below.
public async Task<IActionResult> UploadImage(IFormFile upload)
{
var file = upload;
var allowedExtensions = new[] {
".Jpg", ".png",".PNG",".JPG","JPEG", ".jpg", ".jpeg",".Heif",".tiff"
};
if (file == null) return BadRequest("Null file");
if (file.Length > 10 * 1024 * 1024) return BadRequest("Max file size exceeded.");
if (file.Length == 0) return BadRequest("Empty file");
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
var uploadsFolderPath = Path.Combine(host.WebRootPath, "uploads");
if (!Directory.Exists(uploadsFolderPath))
Directory.CreateDirectory(uploadsFolderPath);
var ext = Path.GetExtension(file.FileName);
// if (allowedExtensions.fin(ext)) return BadRequest("Invalid Image type.");
var filePath = Path.Combine(uploadsFolderPath, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Converting to EST timezone
var timeUtc = DateTime.UtcNow;
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime easternTime = TimeZoneInfo.ConvertTime(timeUtc, easternZone);
var editorID = Guid.NewGuid().ToString();
var editorImages = new editorImages
{
id = editorID,
url = "https://example.com/uploads/" + fileName,
Logintime = easternTime,
fileName = fileName,
};
// Save the image details to your database
ArticlesRepository.saveEditorImagesInfo(editorImages);
await unitofWork.Complete();
var res = await ArticlesRepository.getEdiorImagebyID(editorID);
if (res == null)
// Important to send return the result in this format
return NotFound(new
{
uploaded = false,
error = "Could not upload this image"
});
// Important to send return the result in this format
return Ok(new
{
uploaded = true,
//return imagelocation
url = res.url
});
}