@markitjs/angular
v1.0.4
Published
Angular bindings for @markitjs/core text highlighting engine
Maintainers
Readme
@markitjs/angular
Angular bindings for the @markitjs/core text highlighting engine. Provides a standalone MarkitHighlightDirective and an injectable MarkitService.
Install
npm install @markitjs/angular
# or
bun add @markitjs/angular
# or
pnpm add @markitjs/angular
# or (Yarn — include @markitjs/core explicitly)
yarn add @markitjs/angular @markitjs/corePeer dependencies: Angular 17, 18, or 19, and @markitjs/core (installed automatically by npm 7+, pnpm, and bun; with Yarn, add @markitjs/core explicitly: yarn add @markitjs/angular @markitjs/core).
Quick Start
Directive
Import the standalone directive in your component:
import { Component } from '@angular/core';
import { MarkitHighlightDirective } from '@markitjs/angular';
@Component({
selector: 'app-search',
standalone: true,
imports: [MarkitHighlightDirective],
template: `
<input [(ngModel)]="searchTerm" placeholder="Search..." />
<div [markitHighlight]="searchTerm" [markitOptions]="highlightOptions">
<p>This content will be searched and highlighted.</p>
<app-child></app-child>
<!-- bindings preserved -->
</div>
`,
})
export class SearchComponent {
searchTerm = '';
highlightOptions = { caseSensitive: false, accuracy: 'partially' as const };
}Service
For programmatic control, inject MarkitService:
import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { MarkitService } from '@markitjs/angular';
import type { MarkitInstance } from '@markitjs/core';
@Component({
selector: 'app-editor',
template: `<div #content>Searchable content here.</div>`,
})
export class EditorComponent implements OnDestroy {
@ViewChild('content', { static: true }) contentRef!: ElementRef<HTMLElement>;
private instance: MarkitInstance | null = null;
constructor(private markitService: MarkitService) {}
search(term: string) {
this.instance?.destroy();
this.instance = this.markitService.create(this.contentRef.nativeElement);
this.markitService.highlight(this.instance, term, {
renderer: 'dom',
caseSensitive: false,
});
}
clear() {
this.markitService.clear(this.instance!);
}
ngOnDestroy() {
this.instance?.destroy();
}
}API
MarkitHighlightDirective
Standalone attribute directive. Apply to any element whose text content you want to highlight.
| Input | Type | Description |
| ------------------ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| markitHighlight | string \| string[] | Search term(s) to highlight |
| markitOptions | Partial<MarkitOptions> | All @markitjs/core options (renderer, accuracy, etc.) |
| markitPlugins | MarkitPlugin[] | Plugins to register |
| markitContentKey | string \| number \| (string \| number)[] | When content is dynamic, pass value(s) that change with content so the directive unmarks and re-applies. |
For dynamic content (e.g. bound to a signal), pass [markitContentKey] so highlights re-apply after content updates and avoid garbled text. See Framework lifecycles for how the Angular highlight cycle works.
MarkitService
Injectable service (providedIn: 'root'). All operations run outside NgZone.
| Method | Description |
| --------------------------------------------- | -------------------------- |
| create(element, plugins?) | Create a MarkitInstance |
| highlight(instance, term, options?) | Apply keyword highlighting |
| highlightRegExp(instance, regexp, options?) | Apply regex highlighting |
| clear(instance) | Remove all highlights |
NgZone Safety
Both the directive and service run highlighting outside NgZone to avoid triggering unnecessary change detection cycles. This is especially important for DOM-mutating renderers.
Callbacks (done, noMatch) automatically re-enter the zone, so updating component state from callbacks works correctly:
highlightOptions = {
done: (count: number) => {
this.matchCount = count; // safe — re-enters NgZone automatically
},
};Change Detection Compatibility
| Strategy | Works? | Notes |
| ------------ | ------ | ---------------------------------------------------------- |
| Default | Yes | No unnecessary cycles triggered |
| OnPush | Yes | Changes fire via @Input bindings through OnChanges |
| Signals | Yes | Bind a signal in the template; directive reacts via inputs |
| Zoneless | Yes | Operations already run outside zone |
Preserving Bindings
Unlike innerHTML-based approaches, MarkIt never replaces DOM nodes. The CSS Highlight API (default) creates zero DOM mutations. The DOM wrapping renderer splits text nodes and wraps matches while keeping the original text node in place—including when the match is at the start of the text—so framework bindings (e.g. {{ value }}) continue to update correctly and it never destroys component instances, event listeners, or form control state.
Batched Rendering
For large content areas, enable batched rendering:
<div [markitHighlight]="searchTerm" [markitOptions]="{ batchSize: 500, done: onHighlightDone }">
<!-- large content -->
</div>Performance tip: For large documents or fast typing (e.g. live search), pass debounce and/or batchSize in markitOptions. debounce (ms) reduces re-index/render cycles when the term changes quickly; batchSize splits rendering across animation frames so the UI stays responsive. Both are supported by the core engine and work with the directive and service.
CSS Highlight API Styling
When using the default auto renderer on supported browsers, add this to your global styles:
::highlight(markit-highlight) {
background-color: #fef08a;
color: inherit;
}License
MIT
