qontu / ngx-inline-editor

Native UI Inline-editor Angular (4.0+) component
https://qontu.github.io/ngx-inline-editor
MIT License
164 stars 90 forks source link

Invoking edit from *first* @ViewChildren(InlineEditorComponent).changes subscription event from component AfterViewInit fails #112

Closed KeithGillette closed 6 years ago

KeithGillette commented 6 years ago

I am using ngx-inline-editor@0.2.0-alpha.12 in an Angular 4.4.6 project inside an angular-tree-component template. When I attempt to call the .edit() method on a given inline-editor ViewChild from a subscription handler to a ViewChildren(InlineEditorComponent).changes observable in the component, the inline-editor instance fails to enter editing mode in the template for the first call, but succeeds after that. I can verify that the inline-editor has, in fact, been created and has received the call to .edit even on the first change detected AfterViewInit, as a console.log(inlineEditorInstance.state) of the inline-editor selected from the @ViewChildren(InlineEditorComponent) QueryList shows { editing: true } even though the InlineEditorComponent never displays and an event handler tied to (onEdit) is not called. There are no errors on the Chrome 62.0.3202.94 console.

Here's a simplified version of the component with comments:

import { InlineEditorComponent } from '@qontu/ngx-inline-editor';

@Component({
    selector: 'tt-example-inline-editor-list',
    template: `
        <section>
            <tree-root [nodes]="nodeList">
                <ng-template let-node="node">
                    <inline-editor type="text"
                         [name]="node.id"
                         [(ngModel)]="node.data.name"
                        [hideButtons]="true"
                         [cancelOnEscape]="true"
                        [saveOnEnter]="true"
                        [saveOnBlur]="true"
                        (onEdit)="onInlineEditorEdit($event)">
                    </inline-editor>
                </ng-template>
            </tree-root>
        </section>
    `,
    moduleId: module.id
})
export class ExampleInlineEditorListComponent implements AfterViewInit {
    public nodeList: any[] = [];
        private inlineEditorToInvokeName: string;
    @ViewChildren(InlineEditorComponent) private inlineEditorViewChildren: QueryList<InlineEditorComponent>;

    constructor() {
    // Do something with this.nodeList will eventually create InlineEditorComponent ViewChildren
    // Do something that will eventually set `this.inlineEditorToInvokeName` to a value matching the name of an InlineEditorComponent ViewChild
    }

    public ngAfterViewInit(): void {
        // Subscription emits changes properly from component creation onward & correctly invokes `this.invokeInlineEditor` if this.inlineEditorToInvokeName is defined && the QueryList has members
        this.inlineEditorViewChildren.changes
            .subscribe((queryList: QueryList<InlineEditorComponent>) => {
                if (this.inlineEditorToInvokeName && queryList.length) {
                    setTimeout(() => {
                        this.invokeInlineEditor(this.inlineEditorToInvokeName);
                    }); // setTimeout work-around prevents Angular change detection `ExpressionChangedAfterItHasBeenCheckedError` https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
                }
            });
    }

    private invokeInlineEditor(inlineEditorName: string): void {
        const inlineEditorToInvoke = this.inlineEditorViewChildren.find(
            (inlineEditorComponent: InlineEditorComponent) => {
                return inlineEditorComponent.name === inlineEditorName;
            });
        console.log(inlineEditorToInvoke.state);  // OUTPUT: InlineEditorState {value: "Some Value", disabled: false, editing: false, empty: false}
        if (inlineEditorToInvoke) {
            inlineEditorToInvoke.edit({editing: true, focus: true, select: true});
        }
        console.log(inlineEditorToInvoke.state); // OUTPUT: InlineEditorState {value: "Some Value", disabled: false, editing: true, empty: false}
    }

    private onInlineEditorEdit(editEvent: InlineEditorEvent): void {
        console.log(editEvent); // OUTPUT: Only logs event when inlineEditor appears in template
    }
}

So in that example, the inlineEditorToInvoke only switches to editing mode in the template for calls after the first call to this.invokeInlineEditor(). However, even the first call shows the same console.log(inlineEditorToInvoke.state) output even though the editor never appears. Strangely, however, the this.onInlineEditorEdit() handler tied to the (onEdit) event only logs output on subsequent calls to this.invokeInlineEditor() when the component does in fact enter editing mode in the template.

Any troubleshooting recommendations?!?