@advinsoftwares/ngx-rich-text-editor
v0.3.1
Published
A from-scratch Angular rich text editor (CuteEditor-style: formatting, tables, image/file manager, templates, HTML source view, find/replace) with zero required backend — pluggable persistence via DI tokens, IndexedDB/localStorage defaults included.
Maintainers
Readme
@advinsoftwares/ngx-rich-text-editor
A from-scratch Angular rich text editor built to cover the same ground as
CuteEditor (CuteSoft RTE) — formatting toolbar, tables, an image/file
manager, reusable templates, an HTML source view, and find/replace —
without a commercial license or any required backend. The editing
surface, toolbar, dialogs, undo history, and table logic are all
hand-built on top of the browser's own contenteditable/Selection APIs;
this is not a wrapper around TinyMCE/CKEditor/CuteEditor.
Compatible with Angular 16.2+ (peer dependencies on @angular/core,
@angular/common, @angular/forms, and rxjs).
This README is the quick start. For the full API reference — every input/output, the
ToolbarConfigflags, exactly how content flows in and out, the pluggable persistence services and their contracts, wiring a real backend, and multi-tenant/scalability notes — see GUIDE.md.
Install
npm install @advinsoftwares/ngx-rich-text-editorUsage
import { RichTextEditorModule } from '@advinsoftwares/ngx-rich-text-editor';
@NgModule({
imports: [RichTextEditorModule /* , ... */],
})
export class YourFeatureModule {}<!-- Reactive forms -->
<rte-editor formControlName="bodyHtml"></rte-editor>
<!-- Template-driven -->
<rte-editor [(ngModel)]="bodyHtml"></rte-editor><rte-editor> implements Angular's ControlValueAccessor, so
formControlName, [(ngModel)], validators, disabled state, and
markAsTouched() all work exactly like they would on an <input>. The
value is a plain HTML string.
Trim the toolbar per use case without a second component:
<rte-editor
formControlName="comment"
[toolbarConfig]="{ table: false, image: false, templates: false, fullscreen: false }"
[minHeightPx]="120"
></rte-editor>See the exported ToolbarConfig model for every flag.
What's included
Formatting (bold/italic/underline/strikethrough, font family/size, headings, alignment, lists, blockquote, colors), links, tables (insert + row/column add/delete + per-cell/per-table background color), an image manager (upload, gallery, insert, with an optional built-in downscale/WebP optimizer), a file attachment manager (upload any file type, insert as a clickable chip), a reusable template manager, an HTML source view, find & replace, undo/redo, an in-editor help guide, a fullscreen mode, and paste cleanup that preserves pasted visual formatting (including class-based CSS from Word/Excel/web sources).
Persistence for images, attachments, and templates is 100% pluggable via
Angular DI tokens (IMAGE_ASSET_SERVICE, FILE_ATTACHMENT_SERVICE,
TEMPLATE_SERVICE, all exported from this package), and ships with
zero-config local defaults so it works standalone with no server:
images/attachments in IndexedDB, templates in localStorage. Override
any of the three tokens in your own module to point at a real backend
(a suggested .NET Core Web API + SQL Server + Dapper contract, matching
this package's default service interfaces, is documented in the source
repository's BACKEND-INTEGRATION.md).
Pasting images directly (screenshots, clipboard images)
Copying a screenshot and pasting it straight into the editor (Ctrl+V,
not the toolbar's Insert Image button) embeds it as a base64 data:
URI right inside the saved HTML, rather than going through
IImageAssetService. That's a deliberate choice: a directly-pasted
image has no natural "file" to upload and no dialog to go through, so
embedding it makes the pasted image travel with the document to any
device or user with zero backend and zero local browser storage
involved — nothing to look up, nothing that can go missing.
The tradeoff is size: base64 inflates the image by roughly a third, and
because it's part of the document's own HTML, it's included in every
undo snapshot too (see EditorHistoryService's note on
historyLimit for what that costs at scale). For a document with a
handful of pasted screenshots this is a non-issue; for one with dozens
of large, high-resolution pastes, consider trimming [historyLimit]
lower, or asking users to use the Image Manager's Upload button instead
(which always uses a lightweight URL reference, never inlines bytes).
Images inserted via the toolbar's Insert Image button are
unaffected by this — those always go through IImageAssetService and
use a real, lightweight reference (an IndexedDB-backed object URL by
default, or a permanent server URL if you've wired up
HttpImageAssetService below), never base64.
Resizing images
Click any image in the surface — pasted-as-base64 or inserted via the toolbar, both are handled identically — to select it; four small square handles appear on its corners. Dragging a handle resizes the image with its aspect ratio locked (undistorted resize, the same default most document editors use for images; free distortion isn't offered since it's rarely what's actually wanted). The resize is clamped to the image's own containing element (its parent — a table cell, if that's where it lives — not just the overall editor), and to a 24px floor so it can never be dragged down to invisible.
The new size is written directly onto the <img> as an inline
style="width:…px;height:…px" (replacing any width/height
attributes the toolbar's Image Manager may have set at insert time, so
the element never carries two conflicting size hints), which means it's
a completely normal part of the saved HTML — it survives reload,
travels with the document to any device, and participates in undo/redo
exactly like a formatting change or a paste does. Clicking anywhere
else deselects the image and hides the handles; the handles themselves
are never part of the saved content (they're built and positioned as a
plain DOM overlay outside the contenteditable tree, not inserted into
it), so there's nothing to strip out before saving.
Disabled entirely when [readOnly]/disabled — a read-only document
shouldn't let its images be dragged around.
Wiring uploads to a real endpoint (HttpImageAssetService / HttpFileAttachmentService)
The toolbar's Insert Image and Insert Attachment dialogs both call
upload(file) on whatever service is provided for
IMAGE_ASSET_SERVICE / FILE_ATTACHMENT_SERVICE. Alongside the
zero-config IndexedDB defaults, this package also exports
HttpImageAssetService and HttpFileAttachmentService — ready-made
implementations that POST the file to a configurable endpoint and use
whatever URL the server hands back directly as the <img src> /
attachment href. This is the same "upload once, get back a permanent
URL" pattern used by CuteEditor and most real-world document/image
upload APIs — no base64, no local browser storage, and it works across
every device and user because the file genuinely lives on a server.
import { HttpClientModule } from '@angular/common/http';
import {
RichTextEditorModule,
IMAGE_ASSET_SERVICE,
HttpImageAssetService,
RTE_IMAGE_UPLOAD_ENDPOINT,
FILE_ATTACHMENT_SERVICE,
HttpFileAttachmentService,
RTE_FILE_UPLOAD_ENDPOINT,
} from '@advinsoftwares/ngx-rich-text-editor';
@NgModule({
imports: [HttpClientModule, RichTextEditorModule /* , ... */],
providers: [
{ provide: IMAGE_ASSET_SERVICE, useClass: HttpImageAssetService },
{ provide: RTE_IMAGE_UPLOAD_ENDPOINT, useValue: 'https://api.example.com/api/editor/images' },
{ provide: FILE_ATTACHMENT_SERVICE, useClass: HttpFileAttachmentService },
{ provide: RTE_FILE_UPLOAD_ENDPOINT, useValue: 'https://api.example.com/api/editor/attachments' },
],
})
export class YourFeatureModule {}Each endpoint is expected to behave like BACKEND-INTEGRATION.md's
suggested API surface: POST <endpoint> with the file as
multipart/form-data returns the asset's JSON (id, fileName, url,
sizeBytes, contentType, createdAtUtc); GET <endpoint> lists existing
assets the same shape; DELETE <endpoint>/{id} removes one. You must
import HttpClientModule (or call provideHttpClient()) somewhere in
your app yourself — this library doesn't import it for you, to avoid
the well-known pitfall of registering HttpClientModule's providers
more than once across a module tree.
License
MIT
