In this article, I will build a generic code editor component. I will show the JSON code editor example but it can be changed with the available type of code mode (YAML, Javascript, TypeScript) editor.
For this code editor example, I will use “ace code editor” and it depends on ace-builds package
Note: Ace-builds are just generated files with typescript support. The main editor is ACE
Generate a new fresh angular project
ng new angular-code-editor
Install dependency package
npm i ace-builds
Generate editor module and editor components
ng g m editor ng g c editor --export
I will generate a specific module for our custom editor. When any feature can module import this editor module. those feature module components can be used in our custom editor
Note: editor component will use inside other modules(when the EditorModule was imported) so the editor component must be exported from the EditorModule. “ng g c editor --export
” –export flag will be automatic mark EditorModule export or you can make it manually by add-in EditorModule export section.
Check ace available modes with file name start with “mode-
” prefix in this directory: https://github.com/ajaxorg/ace-builds/tree/master/src
build ace editor in the editor.component.ts file
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, } from '@angular/core'; import { Ace, edit } from 'ace-builds'; import 'ace-builds'; import 'ace-builds/src-noconflict/theme-dracula'; @Component({ selector: 'app-editor', templateUrl: './editor.component.html', styleUrls: ['./editor.component.scss'] }) export class EditorComponent implements OnInit, AfterViewInit, OnChanges { @ViewChild('editor') editorRef!: ElementRef; @Output() textChange = new EventEmitter<string>(); @Input() text!: string; @Input() readOnly: boolean = false; @Input() mode: string = 'json'; @Input() prettify: boolean = true; editor!: Ace.Editor; // All possible options can be found at: // https://github.com/ajaxorg/ace/wiki/Configuring-Ace options = { showPrintMargin: false, highlightActiveLine: true, tabSize: 2, wrap: true, fontSize: 14, fontFamily: '\'Roboto Mono Regular\', monospace', }; constructor() {} ngOnInit(): void { } ngAfterViewInit(): void { this.initEditor_(); } onTextChange(text: string): void { this.textChange.emit(text); } ngOnChanges(changes: SimpleChanges): void { if (!this.editor) { return; } for (const propName in changes) { if (changes.hasOwnProperty(propName)) { switch (propName) { case 'text': this.onExternalUpdate_(); break; case 'mode': this.onEditorModeChange_(); break; default: } } } } private initEditor_(): void { this.editor = edit(this.editorRef.nativeElement); this.editor.setOptions(this.options); this.editor.setValue(this.text, -1); this.editor.setReadOnly(this.readOnly); this.editor.setTheme('ace/theme/dracula'); this.setEditorMode_(); this.editor.session.setUseWorker(false); this.editor.on('change', () => this.onEditorTextChange_()); } private onExternalUpdate_(): void { const point = this.editor.getCursorPosition(); this.editor.setValue(this.text, -1); this.editor.moveCursorToPosition(point); } private onEditorTextChange_(): void { this.text = this.editor.getValue(); this.onTextChange(this.text); } private onEditorModeChange_(): void { this.setEditorMode_(); } private setEditorMode_(): void { this.editor.getSession().setMode(`ace/mode/${this.mode}`); } }
Inside editor.component.html file
Define an element with #editor, where the editor will be rendered in HTML
<div #editor class="app-ace-editor"></div>
Inside editor.component.scss file
The default renders height and width are none. So give editor heigh and weight
.app-ace-editor { width: 100%; min-height: 400px; border: 1px solid gray; }
Let’s give example for this editor use case
Successfully created generic editor. So in this step, I will show JSON, YAML, TypeScript, and SCSS editor. but you can use all of the mode ace builds provided
#1. Import EditorModule
I am going to use our generic editor in the app component. So Import EditorModule in the app.module.ts file
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { EditorModule } from './editor/editor.module'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, EditorModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
#2 edit app.component.ts file
In the example, It will have four editors which are JSON, YAML, typescript, and SCSS mode editor. Every mode will use, just need to import their mode modules. So JSON, YAML, TypeScript, SCSS mode modules were imported.
When ace builds any mode, theme extension mode import, The ‘ace-builds’ parent module must be imported. That’s means ‘ace-builds’ module import required
Next Define editor code data properties, which is jsonInputData for JSON, yamlInputData for YAML, appModuleTsData for typescript, scssData form SCSS editor
import { Component } from '@angular/core'; import 'ace-builds'; import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-yaml'; import 'ace-builds/src-noconflict/mode-typescript'; import 'ace-builds/src-noconflict/mode-scss'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'angular-code-editor'; jsonInputData = ''; yamlInputData = ''; appModuleTsData = ''; scssData = ''; }
#3. render code editor in app.component.html file
Our editor selector is app-editor, So we can use <app-editor>
it as an element. text multiple bind property.
<app-editor [(text)]="jsonInputData" mode="json" ></app-editor>
For example, multiple code components render
<div style="width: 100%;max-width: 1200px;margin: 0 auto;"> <div style="display: grid;grid-template-columns: repeat(2, minmax(0, 1fr));gap: 16px;"> <div> <h2>JSON Editor</h2> <app-editor [(text)]="jsonInputData" mode="json" ></app-editor> </div> <div> <h2>YAML Editor</h2> <app-editor [(text)]="yamlInputData" mode="yaml"></app-editor> </div> <div> <h2>App Module TypeScript Editor <span style="color: blue;">ReadOnly</span></h2> <app-editor [(text)]="appModuleTsData" mode="typescript" [readOnly]="true"></app-editor> </div> <div> <h2>Scss Editor</h2> <app-editor [(text)]="scssData" mode="scss"></app-editor> </div> </div> </div>