什么是处理ViewChildren的最佳角度7钩子

时间:2019-07-04 02:46:54

标签: javascript angular hook

我有一个Angular 7应用程序,正在其中尝试处理ngAfterViewChecked()中的文本输入。

文本输入是mat-tree中的一个节点。它的可见性取决于ngIf条件。如果不满足该条件,则显示跨度。本质上,如果用户双击树中的一个节点(一个span元素),它将变成文本输入,以便用户可以编辑文本:

<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
  <mat-tree-node *matTreeNodeDef="let node">
    <li>
      <span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
      <input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
    </li>
  </mat-tree-node>
  <mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
    <button mat-icon-button matTreeNodeToggle>
      <mat-icon>
        {{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
      </mat-icon>
    </button>
    <span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
    <input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
    <ul [class.collapsed]="!nestedTreeControl.isExpanded(node)">
      <ng-container matTreeNodeOutlet></ng-container>
    </ul>
  </mat-nested-tree-node>
</mat-tree>

当用户双击一个节点时,我不仅希望它变成输入文本,还希望它获得焦点并在其中选择文本。为此,我必须获取本机元素并在其上调用.focus()和.select()。为了获取本机元素,我必须使用ViewChildren(在输入中使用#nodeNameInput进行标记,如您在上面的代码片段中所看到的)。最后,我需要连接ngAfterViewChecked(),以确保ViewChildren的QueryList准备就绪。

以下是该组件的代码:

@ViewChildren('nodeNameInput') nodeNameInputs: QueryList<ElementRef>;

...

ngAfterViewChecked() {
  if (this.nodeNameInputs && this.nodeNameInputs.length) {
    this.nodeNameInputs.first.nativeElement.focus();
    this.nodeNameInputs.first.nativeElement.select();
  }
}

我确保一次只编辑一个节点,所以使用第一个而不是搜索nodeNameInputs来寻找焦点并选择文本是安全的。

这似乎可行,但是存在问题。似乎对于每个按键,都将调用ngAfterViewChecked()。这意味着当用户编辑节点的文本时,每次击键都会重新选择它。这导致用户输入的文本在每次击键时都被覆盖。

我有解决此问题的方法:

ngAfterViewChecked() {
  if (this.nodeNameInputs && this.nodeNameInputs.length) {
    this.nodeNameInputs.first.nativeElement.focus();
    if (!this.keyStroked) {
      this.nodeNameInputs.first.nativeElement.select();
    }
  }
}

......其中keyStroked在keyPressed处理程序中设置,在模糊处理程序中设置为false。

但是我想知道是否还有另一个钩子可以可靠地用于聚焦输入并选择其文本,同时不响应按键。我之所以选择ngAfterViewChecked,是因为测试表明它是每次nodeNameInputs始终准备就绪的唯一挂钩(即this.nodeNameInputs.length始终为1)。但是也许我错过了某些钩子。

我的解决方法似乎很简单。您将如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

创建一个聚焦指令,并将其放置在您要聚焦的输入上,您不必担心生命周期事件。

public delegate IntPtr KeyBoardHook( int nCode, IntPtr wParam, IntPtr lParam);
public class InterceptKeys : IDisposable
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private KeyBoardHook _proc;
    public  event KeyBoardHook  OnKeyBoardKeyClicked;
    private static IntPtr _hookID = IntPtr.Zero;

    public InterceptKeys()
    {
        _proc = HookCallback;
        _hookID = SetHook(_proc);
        if(_hookID == IntPtr.Zero)
        {
            throw new Exception($"Error Happened [{Marshal.GetLastWin32Error()}]");
        }

    }
    public void Dispose()
    {
        UnhookWindowsHookEx(_hookID);
    }

    private IntPtr SetHook(KeyBoardHook proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
               GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    private IntPtr HookCallback(
        int nCode, IntPtr wParam, IntPtr lParam)
    {
        OnKeyBoardKeyClicked?.Invoke(nCode, wParam, lParam);
        //if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        //{
        //    int vkCode = Marshal.ReadInt32(lParam);
        //    Console.WriteLine((char)vkCode);
        //}
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
        KeyBoardHook lpfn, IntPtr hMod, uint dwThreadId);


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
        IntPtr wParam, IntPtr lParam);


    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);


}


    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);


}

并使用它

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[focus]'
})
export class FocusDirective {

  constructor(elm: ElementRef) {
    elm.nativeElement.focus();
  }
}

https://stackblitz.com/edit/angular-qnjw1s?file=src%2Fapp%2Fapp.component.html