在本文中,我们将通过从零开始重新创建指令来了解为什么我们需要ngDoCheck生命周期钩子和KeyValueDiffers服务ngStyle

KeyValueDiffers服务:

KeyValueDiffers服务是一个不同的追踪对象随着时间的推移变化,也暴露了一个API来应对这些变化。(我们稍后会看到我们如何使用这个服务)

ngDoCheck生命周期:

我们都熟悉ngOnChanges生命周期钩子。ngOnChanges如果你Input是一个原始类型或你的Input引用的变化(使用一些不变性策略),你可以实现通知变化。

如果模型引用不改变,但Input模型的某些属性发生变化,则可以实现ngDoCheck生命周期挂钩,以手动构建更改检测逻辑。

好吧,我们谈判就让我们开始编码。

  1. import { Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer } from '@angular/core';
  2. @Directive({selector: '[ngStyle]'})
  3. export class NgStyle implements DoCheck {
  4. private _ngStyle: {[key: string]: string};
  5. private _differ: KeyValueDiffer;
  6. constructor(
  7. private _differs: KeyValueDiffers,
  8. private _ngEl: ElementRef,
  9. private _renderer: Renderer) {}
  10. }

我们正在创建ngStyle指令并实现DoCheck接口。我们还注入我们的服务,KeyValueDiffersRenderer,并参照主机元素(ElementRef)。

@Input()
  set ngStyle(value : {[key: string]: string}) {
    this._ngStyle = value;
    if (!this._differ && value) {
      this._differ = this._differs.find(value).create(null);
    }
  }

我们正在创建ngStyle输入作为setter。如果我们没有什么不同,我们正在创造一个新的不同:

this._differ = this._differs.find(value).create(null);

find()方法只是寻找一个不同的价值。

如果您好奇,这是Angular如何检查我们的价值是否有适当的差异:

return obj instanceof Map || isJsObject(obj);

create()只是创建不同的方法,并返回一个实例·DefaultKeyValueDiffer·。(null为ChangeDetectorRef

ngDoCheck() {
  if (this._differ) {
    const changes = this._differ.diff(this._ngStyle);
    if (changes) {
      this._applyChanges(changes);
    }
  }
}

我们需要实现ngDoCheck生命周期钩子,因为正如我们之前所说的,我们不改变对象引用,所以我们不能使用ngOnChanges

例如:

<div [ngStyle] =“style”> </ div>
//在我们的组件构造函数中
this.style = {color:'red'};
//点击事件后
this.style.color ='blue'

接下来,我们通过调用diff()具有新值的方法来检查我们的对象是否被更改。如果没有任何更改,则返回值将为空。如果有任何更改,返回值将是一个对象,公开三个方法,我们可以使用这些方法来对这些更改作出反应。

private _applyChanges(changes: any): void {
  changes.forEachRemovedItem((record: KeyValueChangeRecord) => 
         this._setStyle(record.key, null));

  changes.forEachAddedItem(
        (record: KeyValueChangeRecord) => 
                 this._setStyle(record.key, record.currentValue));

  changes.forEachChangedItem(
        (record: KeyValueChangeRecord) => 
                 this._setStyle(record.key, record.currentValue));
}

方法是不言自明的,我们可以将回调传递给我们需要知道的每一个变化。每个回调给你一个类型的记录KeyValueChangeRecord.这是一个对象有三个有用的键keycurrentValuepreviousValue.

现在,每当我们有一个变化,我们正在调用从我们的渲染器服务_setStyle()调用setElementStyle()来设置新的元素样式的方法。

你可以在这里阅读更多关于Renderer服务的信息。

private _setStyle(nameAndUnit: string, value: string): void {
  this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
}

事实是,这是Angular的源代码ngStyle。我没有写这个代码,也不记得这个。我只是在解释这些概念,以及它如何在隐藏下工作。

你可以在这里看到完整的源代码。