【问题标题】:How to listen for value changes from class property TypeScript - Angular如何从类属性 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
  • @Elliott08 在您分享的链接中,答案指定它仅在我们使用 @Input 时才有效:至于您对 @Input() 的评论,它有效好吧,当父组件与子交互时,在这种情况下这对您不起作用

标签: javascript angular typescript ecmascript-6 angular5


【解决方案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();
}

【讨论】:

    猜你喜欢
    • 2010-11-07
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 2013-01-09
    • 1970-01-01
    • 2015-05-10
    相关资源
    最近更新 更多