Implement Monaco editor in Angular 13

Viewed 4085

What is the best option to implement Monaco editor in Angular 13? I have seen ngx-monaco-editor, but last update is from 9 months and it’s bumped to Angular 12, also Monaco version there is 0.20.0 (11.02.2020), very old :( Is there another way to use it in Angular 13?

3 Answers

This is how I solved it, heavily inspired by atularen/ngx-monaco-editor. But I also don't want to rely on this dependency. There might be better solutions.

npm install monaco-editor

angular.json:

            "assets": [
              ...
              {
                "glob": "**/*",
                "input": "node_modules/monaco-editor",
                "output": "assets/monaco-editor"
              }
            ],

monaco-editor-service.ts:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class MonacoEditorService {
  loaded: boolean = false;

  public loadingFinished: Subject<void> = new Subject<void>();

  constructor() {}

  private finishLoading() {
    this.loaded = true;
    this.loadingFinished.next();
  }

  public load() {
    // load the assets

    const baseUrl = './assets' + '/monaco-editor/min/vs';

    if (typeof (<any>window).monaco === 'object') {
      this.finishLoading();
      return;
    }

    const onGotAmdLoader: any = () => {
      // load Monaco
      (<any>window).require.config({ paths: { vs: `${baseUrl}` } });
      (<any>window).require([`vs/editor/editor.main`], () => {
        this.finishLoading();
      });
    };

    // load AMD loader, if necessary
    if (!(<any>window).require) {
      const loaderScript: HTMLScriptElement = document.createElement('script');
      loaderScript.type = 'text/javascript';
      loaderScript.src = `${baseUrl}/loader.js`;
      loaderScript.addEventListener('load', onGotAmdLoader);
      document.body.appendChild(loaderScript);
    } else {
      onGotAmdLoader();
    }
  }
}

Now call monacoEditorService.load(), as soon as you need the editor (in my case it's called in app.component.ts in the constructor, to make the editor always available and already preload it).

Now, you can create editors as you please, but make sure to not create them, before Monaco is loaded yet. Like this:

monaco-editor.component.ts

import ...

declare var monaco: any;

@Component({
  selector: 'app-monaco-editor',
  templateUrl: './monaco-editor.component.html',
  styleUrls: ['./monaco-editor.component.scss'],
})
export class MonacoEditorComponent implements OnInit, OnDestroy, AfterViewInit {
  public _editor: any;
  @ViewChild('editorContainer', { static: true }) _editorContainer: ElementRef;

  private initMonaco(): void {
    if(!this.monacoEditorService.loaded) {
      this.monacoEditorService.loadingFinished.pipe(first()).subscribe(() => {
        this.initMonaco();
      });
      return;
    }

    this._editor = monaco.editor.create(
      this._editorContainer.nativeElement,
      options
    );
  }

  ngAfterViewInit(): void {
    this.initMonaco();
  }

There are most probably more elegant solutions than a boolean flag and this subject.

monaco-editor.component.html

Make sure, there is a div in the component, like this:

<div class="editor-container" #editorContainer></div>

Monaco editor for angular

In project directory:

npm i --legacy-peer-deps ngx-monaco-editor
npm i --legacy-peer-deps monaco-editor

update angular.json

"assets": [
   {
     "glob": "**/*",
     "input": "node_modules/monaco-editor",
     "output": "assets/monaco-editor"
   },
   ...
 ],

component.html

<ngx-monaco-editor [options]="codeEditorOptions" [(ngModel)]="code"></ngx-monaco-editor>

component.ts

Component {
    code: string = '';
    codeEditorOptions = {
      theme: 'vs-dark',
      language: 'json',
      automaticLayout: true
    };
    ...
}

module.ts

import {MonacoEditorModule} from 'ngx-monaco-editor';

...

@NgModule({
  ...
  imports: [
    ...,
    MonacoEditorModule.forRoot()
    ...
  ],
  ...
})

It worked for me :)

Related