如何监听TypeScript - Angular类属性的值变化

44
在AngularJS中,我们可以使用$watch$digest等方式监听变量的变化,但是在新版本的Angular(5、6)中,这种方式已经不再可行。
在Angular中,这种行为现在已成为组件生命周期的一部分。
我查阅了官方文档、文章,特别是Angular 5对可变对象的变更检测,以找出如何监听TypeScript类/Angular中变量(类属性)的变化。
今天提出的建议是:

import { OnChanges, SimpleChanges, DoCheck } from '@angular/core';


@Component({
  selector: 'my-comp',
  templateUrl: 'my-comp.html',
  styleUrls: ['my-comp.css'],
  inputs:['input1', 'input2']
})
export class MyClass implements OnChanges, DoCheck, OnInit{

  //I can track changes for this properties
  @Input() input1:string;
  @Input() input2:string;
  
  //Properties what I want to track !
  myProperty_1: boolean
  myProperty_2: ['A', 'B', 'C'];
  myProperty_3: MysObject;

  constructor() { }

  ngOnInit() { }

  //Solution 1 - fired when Angular detects changes to the @Input properties
  ngOnChanges(changes: SimpleChanges) {
    //Action for change
  }

  //Solution 2 - Where Angular fails to detect the changes to the input property
  //the DoCheck allows us to implement our custom change detection
  ngDoCheck() {
    //Action for change
  }
}

这仅适用于@Input()属性!

如果我想跟踪组件自己的属性(myProperty_1, myProperty_2myProperty_3)的更改,这将不起作用。

有人能帮我解决这个问题吗?最好是与Angular 5兼容的解决方案


3
你可以使用 Observable。 - Elliott08
请参考:https://dev59.com/YVgQ5IYBdhLWcg3w-48d - Elliott08
1
@Elliott08 在你分享的链接中,答案指出它只有在使用 @Input 时才能正常工作:关于 @Input() 的评论,它在父组件-子组件交互时效果很好,所以在这种情况下它并不适用于你 - L Y E S - C H I O U K H
5个回答

35

你仍然可以通过使用 KeyValueDiffersDoCheck 生命周期钩子来检查组件的字段成员值的更改。

import { DoCheck, KeyValueDiffers, KeyValueDiffer } from '@angular/core';

differ: KeyValueDiffer<string, any>;
constructor(private differs: KeyValueDiffers) {
  this.differ = this.differs.find({}).create();
}

ngDoCheck() {
  const change = this.differ.diff(this);
  if (change) {
    change.forEachChangedItem(item => {
      console.log('item changed', item);
    });
  }
}

查看演示.


@ValeSteve DoCheck 只在变更检测后运行。 - Pengyy
9
好的,如果你仔细研究一下,变化检测其实很常运行 :) - Valex
1
在我看来,这个解决方案最接近我想要的,并且是最合乎逻辑的,它利用了Angular框架的优势,而不是重新发明轮子并重写Angular已经定义的内容。然而,我对ChangeDetectorRef或DoCheck的使用有些困惑! https://github.com/angular/angular/blob/5.2.9/packages/core/src/change_detection/change_detector_ref.ts#L1-L192 - L Y E S - C H I O U K H
3
ChangeDetectorRef 使你能够手动触发 Angular 的变更检测。DoCheck 是在变更检测之后触发的生命周期钩子。这就是它们之间的关系。 - Pengyy
在 Angular 13 中无法工作! - M Komaei
显示剩余2条评论

5
我认为解决你的问题最好的方法是使用一个装饰器,自动替换原始字段为一个属性,然后在setter中,您可以创建一个类似于angular创建的SimpleChanges对象,以便使用与angular相同的通知回调(或者您可以为这些通知创建不同的接口,但是同样的原则适用)。
import { OnChanges, SimpleChanges, DoCheck, SimpleChange } from '@angular/core';

function Watch() : PropertyDecorator & MethodDecorator{
    function isOnChanges(val: OnChanges): val is OnChanges{
        return !!(val as OnChanges).ngOnChanges
    }
    return (target : any, key: string | symbol, propDesc?: PropertyDescriptor) => {
        let privateKey = "_" + key.toString();
        let isNotFirstChangePrivateKey = "_" + key.toString() + 'IsNotFirstChange';
        propDesc = propDesc || {
            configurable: true,
            enumerable: true,
        };
        propDesc.get = propDesc.get || (function (this: any) { return this[privateKey] });

        const originalSetter = propDesc.set || (function (this: any, val: any) { this[privateKey] = val });

        propDesc.set = function (this: any, val: any) {
            let oldValue = this[key];
            if(val != oldValue) {
                originalSetter.call(this, val);
                let isNotFirstChange = this[isNotFirstChangePrivateKey];
                this[isNotFirstChangePrivateKey] = true;
                if(isOnChanges(this)) {
                    var changes: SimpleChanges = {
                        [key]: new SimpleChange(oldValue, val, !isNotFirstChange)
                    }
                    this.ngOnChanges(changes);
                }
            }
        }
        return propDesc;
    }
}

// Usage
export class MyClass implements OnChanges {


    //Properties what I want to track !
    @Watch()
    myProperty_1: boolean  =  true
    @Watch()
    myProperty_2 =  ['A', 'B', 'C'];
    @Watch()
    myProperty_3 = {};

    constructor() { }
    ngOnChanges(changes: SimpleChanges) {
        console.log(changes);
    }
}

var myInatsnce = new MyClass(); // outputs original field setting with firstChange == true
myInatsnce.myProperty_2 = ["F"]; // will be notified on subsequent changes with firstChange == false

1
谢谢您的回复。在我们需要跟踪多个类属性的情况下,实现这个解决方案似乎很困难。 如果您想在两个类属性同时更改时启动操作,如何使用此解决方案设置? - L Y E S - C H I O U K H
@LyesCHIOUKH,有几种方法可以做到这一点,其中两种我能想到的是:第一种是将通知设置为定时器,在不立即触发它的情况下进行。另一种方法是使用某些方法调用显式地批处理通知。我可以更改示例以使用任一版本,第一种版本更容易实现。你对任何一种解决方案感兴趣吗? - Titian Cernicova-Dragomir
1
谢谢您的回复。您认为 #Pengyy 建议的结合 KeyValueDiffer + DoCheck 的解决方案怎么样? - L Y E S - C H I O U K H
@LyesCHIOUKH 看起来它运行良好,而且似乎是更符合Angular方式的做法,在你的情况下可能是更好的选择。我的解决方案在Angular之外也几乎可以达到同样的效果 :)。 - Titian Cernicova-Dragomir
1
谢谢@Titian Cernicova-Dragomir。实际上,我会选择最适合Angular的解决方案 :) - L Y E S - C H I O U K H
@TitianCernicova-Dragomir,你能否创建一个不依赖于Angular的版本? - hackp0int

3

正如所说,您可以使用

public set myProperty_2(value: type): void {
 if(value) {
  //doMyCheck
 }

 this._myProperty_2 = value;
}

接下来,如果您需要检索它

public get myProperty_2(): type {
  return this._myProperty_2;
}

这样,您可以在设置/获取变量时执行所有检查,这样的方法将在每次设置/获取myProperty_2属性时触发。

小型演示:https://stackblitz.com/edit/angular-n72qlu


2
这个解决方案可行,但并不是最优的。例如,想象一下我们想要跟踪两个变量的更改以触发某些操作!设置会更加复杂。。 - L Y E S - C H I O U K H
很不幸,你必须使用Angular提供的doCheck。 - Valex

3

我认为我找到了一种监听DOM更改的方法,可以获取对元素所做的任何更改。我真诚地希望以下提示和技巧能帮助你解决问题,按照以下简单步骤进行:

首先,你需要像这样引用你的元素:

在HTML中:

<section id="homepage-elements" #someElement>
....
</section>

在该组件的TS文件中:

@ViewChild('someElement')
public someElement: ElementRef;

其次,您需要创建一个观察者来监听该元素的更改,您需要将组件的ts文件制作为implements AfterViewInit, OnDestroy,然后在那里实现ngAfterViewInit()OnDestroy稍后再处理):

private changes: MutationObserver;
ngAfterViewInit(): void {
  console.debug(this.someElement.nativeElement);

  // This is just to demo 
  setInterval(() => {
    // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
    this.renderer.setAttribute(this.someElement.nativeElement, 'my_custom', 'secondNow_' + (new Date().getSeconds()));
  }, 5000);

  // Here is the Mutation Observer for that element works
  this.changes = new MutationObserver((mutations: MutationRecord[]) => {
      mutations.forEach((mutation: MutationRecord) => {
        console.debug('Mutation record fired', mutation);
        console.debug(`Attribute '${mutation.attributeName}' changed to value `, mutation.target.attributes[mutation.attributeName].value);
      });
    }
  );

  // Here we start observer to work with that element
  this.changes.observe(this.someElement.nativeElement, {
    attributes: true,
    childList: true,
    characterData: true
  });
}

您会看到控制台将随着对该元素的任何更改而工作:

enter image description here

这是另一个例子,您将看到触发了2个变异记录,并且更改了类:

// This is just to demo
setTimeout(() => {
   // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds()));
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds() + 1));
}, 5000);

// Here is the Mutation Observer for that element works
this.changes = new MutationObserver((mutations: MutationRecord[]) => {
    mutations.forEach((mutation: MutationRecord) => {
      console.debug('Mutation record fired', mutation);
      if (mutation.attributeName == 'class') {
        console.debug(`Class changed, current class list`, mutation.target.classList);
      }
    });
  }
);

控制台日志:

enter image description here

还有一些清理工作:OnDestroy

ngOnDestroy(): void {
  this.changes.disconnect();
}

最后,您可以参考这篇文章:使用Angular中的MutationObserver监听DOM更改


谢谢您的回复。在我看来,如果我们需要跟踪多个类属性,这个解决方案似乎很难实现! - L Y E S - C H I O U K H
@LyesCHIOUKH,你可以筛选出你想要的属性,并为每个属性添加observable,如果你想单独跟踪每个属性,这只是一个提示或建议,可以作为开始。例如:... mutations.filter((mutation: MutationRecord) => mutation.attributeName == 'myProperty1').forEach( ... - Al-Mothafar

0

你可以导入ChangeDetectorRef

 constructor(private cd: ChangeDetectorRef) {
          // detect changes on the current component
            // this.cd is an injected ChangeDetector instance
            this.cd.detectChanges();

            // or run change detection for the all app
            // this.appRef is an ApplicationRef instance
            this.appRef.tick();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接