推迟在Angular中使用指令功能

最近,我不得不解决将通过组件库实现的用于显示工具提示的旧机制更改为新机制的问题。和往常一样,我决定不发明自行车。为了开始解决这个问题,我开始寻找一个用纯JavaScript编写的开源库,该库可以放在Angular指令中并以这种形式使用。 就我而言,由于我经常使用popper.js,所以我发现了tippy.js



由同一位开发人员撰写。对我来说,这样的图书馆似乎是解决问题的完美解决方案。tippy.js库具有广泛的功能集。借助它的帮助,您可以创建工具提示(工具提示元素)以及许多其他元素。这些元素可以使用主题进行自定义,它们速度快,类型强,提供内容的可访问性并具有许多其他有用的功能。

我首先为tippy.js创建包装指令:

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  private instance: Instance;
  private _content: string;

  get content() {
    return this._content;
  }

  @Input('tooltip') set content(content: string) {
    this._content = content;
    if (this.instance) this.instance.setContent(content);
  }

  constructor(private host: ElementRef<Element>, private zone: NgZone) {}

  ngAfterViewInit() {
    this.zone.runOutsideAngular(() => {
      this.instance = tippy(this.host.nativeElement, {
        content: this.content,
      });
    });
}

通过调用函数tippy并向其传递元素host创建工具提示content。此外,tippy由于不需要记录tippy触发变更检测周期的事件,因此我们在Angular Zone之外进行调用

现在,我们将在大量700个元素中使用工具提示:

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ngFor="let item of data" [tooltip]="item.label">
         {{ item.label }}
      </li>
    </ul>
  `
})
export class AppComponent {
  data = Array.from({ length: 700 }, (_, i) => ({
    id: i,
    label: `Value ${i}`,
  }));
}

一切正常。每个项目都会显示一个工具提示。但是我们可以更好地解决这个问题。在我们的例子中,创建了700份tippy。对于每个元素,tippy.js工具添加了4个事件侦听器。这意味着我们注册了2800个侦听器(700 * 4)。

要亲自查看,可以使用getEventListenersChrome开发者工具控制台中的方法。视图构造getEventListeners(element)返回有关为给定元素注册的事件侦听器的信息。


所有事件侦听器的摘要

如果以这种形式保留代码,则可能会影响应用程序的内存消耗以及首次呈现时的内存消耗。对于移动设备上的页面输出尤其如此。让我们考虑一下。我是否需要为tippy未在视口中显示的项目创建实例?不,不需要。

我们将使用APIIntersectionObserver延迟工具提示支持的包含,直到该项目出现在屏幕上。如果您不熟悉APIIntersectionObserver,请查看文档。 

IntersectionObserver观察对象表示包装器创建

const hasSupport = 'IntersectionObserver' in window;

export function inView(
  element: Element,
  options: IntersectionObserverInit = {
    root: null,
    threshold: 0.5,
  }
) {
  return new Observable((subscriber) => {
    if (!hasSupport) {
      subscriber.next(true);
      subscriber.complete();
    }

    const observer = new IntersectionObserver(([entry]) => {
      subscriber.next(entry.isIntersecting);
    }, options);

    observer.observe(element);

    return () => observer.disconnect();
  });
}

我们创建了一个可观察对象,该对象将元素与给定区域相交的时间通知订阅者。另外,这里我们检查IntersectionObserver浏览器支持如果浏览器不支持IntersectionObserver,我们只需发出true并退出即可。IE用户自己应该为自己的苦难负责。

现在,inView我们可以在实现工具提示功能的指令中使用观察到的对象

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  ...

  ngAfterViewInit() {
    //   
    inView(this.host.nativeElement).subscribe((inView) => {
      if (inView && !this.instance) {
        this.zone.runOutsideAngular(() => {
          this.instance = tippy(this.host.nativeElement, {
            content: this.content,
          });
        });
      } else if (this.instance) {
        this.instance.destroy();
        this.instance = null;
      }
    });
  }
}

再次运行代码以分析事件侦听器的数量。


项目

优秀完成后所有事件侦听器的摘要现在,我们仅为可见元素创建工具提示。

让我们寻找与新解决方案有关的几个问题的答案。

我们为什么不使用虚拟滚动来解决此问题?虚拟滚动不能在任何情况下使用。并且,此外,Angular Material库会缓存模板,因此,相应的数据将继续占用内存。

那事件委托呢?为此,您需要自己实现其他机制,在Angular中,没有通用的方法可以解决此问题。

摘要


在这里,我们讨论了如何推迟指令功能的应用。这使应用程序可以更快地加载并消耗更少的内存。工具提示示例只是可以应用类似技术的许多情况之一。我相信您会找到许多在自己的项目中使用它的方法。

您将如何解决显示大量元素的问题,每个元素都需要配备工具提示?

我们提醒您,我们正在继续进行预测竞赛,您可以在其中赢得全新的iPhone。仍有时间进行深入分析,并对主题价值做出最准确的预测。


All Articles