angularace-editorangular14viewchild

HTML element is not found at ngAfterViewInit


I have an Angular component that injects Ace editor. Sometimes in logs (very rare) I can see the following error:

Error: ace.edit can't find div #aceEditor
    at ie.edit (https://stepindev.com/ru/main.fcb9335868d1e005.js:1:1267133)
    at t.ngAfterViewInit (https://stepindev.com/ru/main.fcb9335868d1e005.js:1:467814)

I have never been able to reproduce the issue. And I do not understand what can cause it. My understanding is that at the moment when ngAfterViewInit starts, the DOM is ready and can be used.

So the question is what can be the cause of the problem?

Component's template:

<div id="aceEditor" #editor></div>

<!--Don't recreate console with *ngIf. It must exists all the time to be able to capture all console input/output-->
<sid-console #console class="console" (change)="onIOAppeared()" hidden></sid-console>

<ng-content></ng-content>

ngAfterViewInit handler

    import * as ace from 'ace-builds'

    ...

    ngAfterViewInit(): void {
        this.editor = ace.edit('aceEditor');
        this.editor.setTheme('ace/theme/textmate');
        this.editor.session.setMode('ace/mode/python');
        this.editor.setOptions({
            fontSize: '16pt'
        });

        this.editor.on("change", delta => this.change.emit(delta))
        window.addEventListener(this.resizeEvenName, this.resizeEventListener, false);

        // resize after all components above this one are settled. So we can get the correct "top" value
        window.setTimeout(() => this.resizeComponents(), 0);
    }

The container with this component is created dynamically depending on an HTTP request (using *ngIf):

<sid-robot-python-view [task]="$any(task)" *ngIf="isRobotPythonTask()" data-testId="robot-python-view"></sid-robot-python-view>

I would expect the the error never happens. It was logged for Chrome 130.0 and Chrome 109.0 on Windows. I use Angular 14.


Solution

  • According to the Ace editor docs - Edit Function accepts an element instead of a string, could you try this method instead of passing in string.

    Here we take a ViewChild of the element, then we pass the nativeElement to the ace editor edit function.

    import * as ace from 'ace-builds'
    ...
    
    ...
    @ViewChild('aceEditor') aceEditor: ElementRef<any>;
    ...
    
    ngAfterViewInit(): void {
        if(this.editor?.nativeElement) {
            this.editor = ace.edit(this.editor.nativeElement);            
            this.editor.setTheme('ace/theme/textmate');
            this.editor.session.setMode('ace/mode/python');
            this.editor.setOptions({
                fontSize: '16pt'
            });
    
            this.editor.on("change", delta => this.change.emit(delta))
            window.addEventListener(this.resizeEvenName, this.resizeEventListener, false);
    
            // resize after all components above this one are settled. So we can get the correct "top" value
            window.setTimeout(() => this.resizeComponents(), 0);
        }
    }