如何从类属性 TypeScript 中侦听值更改 - Angular

Posted

技术标签:

【中文标题】如何从类属性 TypeScript 中侦听值更改 - Angular【英文标题】:How to listen for value changes from class property TypeScript - Angular 【发布时间】:2018-09-08 04:28:44 【问题描述】:

在 AngularJS 中,我们可以使用 $watch$digest... 监听变量变化,而新版本的 Angular (5, 6) 不再支持。

在 Angular 中,这种行为现在是组件生命周期的一部分。

我查看了官方文档、文章,尤其是Angular 5 change detection on mutable objects,以了解如何在TypeScript class / 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_1myProperty_2myProperty_3)的更改,这将不起作用。

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

【问题讨论】:

你可以使用 Observable 参考:***.com/questions/41915625/… @Elliott08 在您分享的链接中,答案指定它仅在我们使用 @Input 时才有效:至于您对 @Input() 的评论,它有效好吧,当父组件与子交互时,在这种情况下这对您不起作用 【参考方案1】:

您仍然可以通过KeyValueDiffers 通过DoCheck lifehook 检查组件的字段成员值变化。

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);
    );
  

demo

【讨论】:

这会起作用,但我会说这不是最好的解决方案,因为 lifeCycle 挂钩真的经常运行。 @ValeSteve DoCheck 仅在更改检测后运行。 好吧,如果您查看它,更改检测会经常运行 :) 在我看来,这个解决方案是最接近我想要的,也是最合乎逻辑的,它利用了 Angular 框架的优点,而无需重新发明***并重写 angular 已经定义的内容。不过,我对 ChangeDetectorRef 或 DoCkeck 的使用有点困惑! github.com/angular/angular/blob/5.2.9/packages/core/src/… @LyesCHIOUKH ChangeDetectorRef 让您手动触发角度的变化检测。 DoCheck 是生命钩子,检测到变化后会触发。这就是他们的关系。【参考方案2】:

我认为对您的问题最好的解决方案是使用一个装饰器,该装饰器自动将原始字段替换为一个属性,然后在设置器上,您可以创建一个类似于 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

【讨论】:

感谢您的回复。在我看来,在我们有几个类属性要跟踪的情况下,这个解决方案很难实现。如果您想在两个类属性同时更改时启动操作,您如何使用此解决方案进行设置? @LyesCHIOUKH 有几种方法可以做到这一点,我想到了两种方法:第一种是将通知放在计时器上,而不是立即触发。另一种是使用一些方法调用显式地批量通知。我可以将示例更改为我们的任一版本,第一个版本更易于实现。您对这两种解决方案都感兴趣吗? 感谢您的回复。您如何看待#Pengyy 提出的结合 KeyValueDiffer + DoCheck 的解决方案? @LyesCHIOUKH 它似乎工作正常,而且它似乎是一种更 Angular 的做事方式,在你的情况下这可能是更好的选择。我的解决方案也可以使用几乎相同的外部角度:)。 谢谢@Titian Cernicova-Dragomir。实际上,我会选择最适合 Angular 的解决方案 :)【参考方案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

【讨论】:

此解决方案有效,但不是最优的。举个例子,假设我们想要跟踪两个变量的变化来触发一个动作!设置起来会更复杂.. 不幸的是,你必须使用 angular 提供的 doCheck【参考方案4】:

我想我是来听 DOM 变化的,你可以得到对你的元素所做的任何变化,我真的希望这些提示和技巧能帮助你解决你的问题,遵循以下简单的步骤:

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

在 HTML 中:

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

并且在该组件的 TS 文件中:

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

第二,你需要创建一个观察者来监听那个元素的变化,你需要把你的组件ts文件变成implements AfterViewInit, OnDestroy,然后在那里实现ngAfterViewInit()(@987654331 @以后有工作):

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
  );

您将看到控制台将适用于对该元素的任何更改:

这是另一个示例,您将看到 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);
      
    );
  
);

控制台日志:

还有家务,OnDestroy:

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

最后,您可以查看此参考:Listening to DOM Changes Using MutationObserver in Angular

【讨论】:

感谢您的回复。在我看来,在我们有多个类属性要跟踪的情况下,这个解决方案很难实现! @LyesCHIOUKH 如果您想单独跟踪每个属性,您可以过滤所需的属性并为每个属性添加 observable,这只是提示或提示。例如... mutations.filter((mutation: MutationRecord) =&gt; mutation.attributeName == 'myProperty1').forEach( ...【参考方案5】:

你可以导入 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();

【讨论】:

以上是关于如何从类属性 TypeScript 中侦听值更改 - Angular的主要内容,如果未能解决你的问题,请参考以下文章

Javascript 对象中属性值更改的侦听器

spring - 从类的静态字段中的属性文件中读取属性值

值更改侦听器在 primefaces 日历中不起作用

从类更改组件装饰器 Angular 2 的属性

在 Python(tkinter)中从类的外部更改类的私有属性(标签)

从类对象列表中选择时设置属性值