Angular cron editor
npm install ngx-ntk-cron-editorbash
npm install ngx-ntk-cron-editor
`
π¦ Features
$3
- Visual Expression Builder - Intuitive cron expression creation
- Time Picker Component - Hour and minute selection
- Real-time Validation - Instant validation feedback
- Multi-language Support - English, Persian, Russian
- Customizable Options - Flexible configuration
- Expression Preview - Human-readable preview
- Next Run Calculation - Shows next execution time
$3
- Multiple Cron Formats - Standard and extended formats
- Custom Validation Rules - User-defined validation
- Expression Import/Export - Save and load expressions
- Responsive Design - Mobile-friendly interface
- Accessibility - ARIA support and keyboard navigation
- Theme Integration - Customizable styling
π§ Usage
$3
`typescript
import { NgModule } from "@angular/core";
import { NgxNtkCronEditorModule } from "ngx-ntk-cron-editor";
@NgModule({
imports: [NgxNtkCronEditorModule],
})
export class AppModule {}
`
$3
`html
`
$3
`typescript
import { Component } from "@angular/core";
import { CronOptions } from "ngx-ntk-cron-editor";
export class CronEditorComponent {
cronExpression = "0 0 12 ?"; // Daily at noon
cronOptions: CronOptions = {
includeYear: true,
includeSeconds: true,
includeMonth: true,
includeDayOfWeek: true,
includeDayOfMonth: true,
includeHour: true,
includeMinute: true,
allowMultiple: {
seconds: true,
minutes: true,
hours: true,
daysOfMonth: true,
months: true,
daysOfWeek: true,
years: true,
},
allowRange: {
seconds: true,
minutes: true,
hours: true,
daysOfMonth: true,
months: true,
daysOfWeek: true,
years: true,
},
allowStep: {
seconds: true,
minutes: true,
hours: true,
daysOfMonth: true,
months: true,
daysOfWeek: true,
years: true,
},
};
onExpressionChange(expression: string): void {
console.log("Cron expression changed:", expression);
// Handle expression change
}
onValidationError(error: any): void {
console.error("Validation error:", error);
// Handle validation error
}
onNextRunCalculated(nextRun: Date): void {
console.log("Next run:", nextRun);
// Handle next run calculation
}
}
`
π API Reference
$3
| Property | Type | Default | Description |
| ---------------- | ----------- | -------------- | --------------------------- |
| cronExpression | string | - | Cron expression string |
| options | CronOptions | defaultOptions | Configuration options |
| language | string | 'en' | Interface language |
| disabled | boolean | false | Disable editor |
| readonly | boolean | false | Read-only mode |
| showPreview | boolean | true | Show human-readable preview |
| showNextRun | boolean | true | Show next execution time |
$3
| Event | Type | Description |
| ------------------- | ------------ | -------------------------- |
| expressionChange | EventEmitter | Expression change event |
| validationError | EventEmitter | Validation error event |
| nextRunCalculated | EventEmitter | Next run calculation event |
| timePickerChange | EventEmitter | Time picker change event |
$3
`typescript
interface CronOptions {
includeYear?: boolean;
includeSeconds?: boolean;
includeMonth?: boolean;
includeDayOfWeek?: boolean;
includeDayOfMonth?: boolean;
includeHour?: boolean;
includeMinute?: boolean;
allowMultiple?: {
seconds?: boolean;
minutes?: boolean;
hours?: boolean;
daysOfMonth?: boolean;
months?: boolean;
daysOfWeek?: boolean;
years?: boolean;
};
allowRange?: {
seconds?: boolean;
minutes?: boolean;
hours?: boolean;
daysOfMonth?: boolean;
months?: boolean;
daysOfWeek?: boolean;
years?: boolean;
};
allowStep?: {
seconds?: boolean;
minutes?: boolean;
hours?: boolean;
daysOfMonth?: boolean;
months?: boolean;
daysOfWeek?: boolean;
years?: boolean;
};
}
`
π Internationalization
The cron editor supports multiple languages:
$3
- English (en) - Default language
- Persian/Farsi (fa) - ΩΨ§Ψ±Ψ³Ϋ
- Russian (ru) - Π ΡΡΡΠΊΠΈΠΉ
$3
`typescript
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { HttpClient } from "@angular/common/http";
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, "./assets/i18n/cron-editor/", ".json");
}
@NgModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
],
})
export class AppModule {}
`
$3
`json
{
"cron-editor": {
"seconds": "Seconds",
"minutes": "Minutes",
"hours": "Hours",
"dayOfMonth": "Day of Month",
"month": "Month",
"dayOfWeek": "Day of Week",
"year": "Year",
"every": "Every",
"at": "At",
"on": "On",
"and": "And",
"or": "Or",
"between": "Between",
"starting": "Starting",
"increment": "Increment",
"last": "Last",
"first": "First",
"second": "Second",
"third": "Third",
"fourth": "Fourth",
"fifth": "Fifth",
"weekday": "Weekday",
"weekend": "Weekend",
"nextRun": "Next Run",
"expression": "Expression",
"preview": "Preview",
"validation": "Validation"
}
}
`
π¨ Customization
$3
`scss
// Custom cron editor styles
.ngx-ntk-cron-editor {
.cron-tabs {
background-color: #f5f5f5;
border-radius: 4px;
padding: 10px;
.tab-button {
padding: 8px 16px;
margin: 0 4px;
border: none;
border-radius: 4px;
background-color: transparent;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: #e0e0e0;
}
&.active {
background-color: #2196f3;
color: white;
}
}
}
.cron-field {
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fafafa;
.field-label {
font-weight: 500;
color: #333;
margin-bottom: 5px;
}
.field-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.time-picker {
display: flex;
gap: 5px;
align-items: center;
select {
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
background-color: white;
}
}
}
.expression-preview {
margin-top: 15px;
padding: 10px;
background-color: #e3f2fd;
border-radius: 4px;
border-left: 4px solid #2196f3;
.preview-text {
font-family: monospace;
color: #1976d2;
}
}
.validation-error {
color: #f44336;
font-size: 12px;
margin-top: 5px;
}
}
`
$3
`html
Expression Preview
{{ expression }}
Human Readable: {{ preview }}
`
π Validation
$3
`typescript
export class CronEditorComponent {
customValidationRules = {
maxDaysInMonth: 31,
maxHours: 23,
maxMinutes: 59,
maxSeconds: 59,
allowSpecificDays: ["MON", "TUE", "WED", "THU", "FRI"],
businessHoursOnly: true,
excludeWeekends: true,
};
validateExpression(expression: string): boolean {
// Custom validation logic
const parts = expression.split(" ");
// Validate seconds
if (parts[0] && !this.isValidSeconds(parts[0])) {
return false;
}
// Validate minutes
if (parts[1] && !this.isValidMinutes(parts[1])) {
return false;
}
// Validate hours
if (parts[2] && !this.isValidHours(parts[2])) {
return false;
}
// Validate day of month
if (parts[3] && !this.isValidDayOfMonth(parts[3])) {
return false;
}
// Validate month
if (parts[4] && !this.isValidMonth(parts[4])) {
return false;
}
// Validate day of week
if (parts[5] && !this.isValidDayOfWeek(parts[5])) {
return false;
}
return true;
}
private isValidSeconds(seconds: string): boolean {
const value = parseInt(seconds);
return value >= 0 && value <= 59;
}
private isValidMinutes(minutes: string): boolean {
const value = parseInt(minutes);
return value >= 0 && value <= 59;
}
private isValidHours(hours: string): boolean {
const value = parseInt(hours);
return value >= 0 && value <= 23;
}
private isValidDayOfMonth(day: string): boolean {
const value = parseInt(day);
return value >= 1 && value <= 31;
}
private isValidMonth(month: string): boolean {
const value = parseInt(month);
return value >= 1 && value <= 12;
}
private isValidDayOfWeek(day: string): boolean {
const value = parseInt(day);
return value >= 1 && value <= 7;
}
}
`
π§ͺ Testing
$3
`typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxNtkCronEditorModule } from "ngx-ntk-cron-editor";
describe("CronEditorComponent", () => {
let component: CronEditorComponent;
let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NgxNtkCronEditorModule],
declarations: [CronEditorComponent],
}).compileComponents();
fixture = TestBed.createComponent(CronEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should initialize with default expression", () => {
expect(component.cronExpression).toBe("0 0 12 ?");
});
it("should handle expression change", () => {
const newExpression = "0 30 9 ?";
spyOn(component.expressionChange, "emit");
component.cronExpression = newExpression;
component.onExpressionChange(newExpression);
expect(component.expressionChange.emit).toHaveBeenCalledWith(newExpression);
});
it("should validate cron expression correctly", () => {
const validExpression = "0 0 12 ?";
const invalidExpression = "60 0 12 ?";
expect(component.validateExpression(validExpression)).toBe(true);
expect(component.validateExpression(invalidExpression)).toBe(false);
});
it("should calculate next run time", () => {
const expression = "0 0 12 ?";
const nextRun = component.calculateNextRun(expression);
expect(nextRun).toBeInstanceOf(Date);
expect(nextRun.getHours()).toBe(12);
expect(nextRun.getMinutes()).toBe(0);
expect(nextRun.getSeconds()).toBe(0);
});
});
`
π Performance
$3
1. Lazy Loading: Load cron editor on demand
2. Debounced Updates: Debounce expression change events
3. Caching: Cache validation results
4. Virtual Scrolling: Use for large option lists
5. Memoization: Cache next run calculations
$3
`typescript
export class CronEditorComponent implements OnDestroy {
private destroy$ = new Subject();
private validationCache = new Map();
ngOnInit(): void {
// Subscribe to expression changes with debounce
this.expressionChange$.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe((expression) => {
this.validateExpression(expression);
});
}
validateExpression(expression: string): boolean {
// Check cache first
if (this.validationCache.has(expression)) {
return this.validationCache.get(expression)!;
}
// Perform validation
const isValid = this.performValidation(expression);
// Cache result
this.validationCache.set(expression, isValid);
return isValid;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.validationCache.clear();
}
}
``