【问题标题】:Angular 4: How to watch an object for changes?Angular 4:如何观察对象的变化?
【发布时间】:2018-03-01 23:42:45
【问题描述】:

ETA:我知道有多种方法可以查看我的form 的变化。那不是我想要做的。正如标题所说,我在问如何观察一个对象的变化。下面显示的应用程序仅用于说明目的。请回答我提出的问题。谢谢!

我有这个简单的应用程序:

import { Component, OnInit } from '@angular/core';

export class Customer {
    firstName: string;
    favoriteColor: string;
}

@Component({
    selector: 'my-app',
    template: `
        <div *ngIf="customer">
            <input type="text" [(ngModel)]="customer.firstName">
            <input type="text" [(ngModel)]="customer.favoriteColor">
        </div>
        `
})
export class AppComponent implements OnInit {

    private customer: Customer;

    ngOnInit(): void {

        this.customer = new Customer();

        // TODO: how can I register a callback that will run whenever
        // any property of this.customer has been changed?

    }

}

注意 TODO。我需要注册一个回调,该回调将在 this.customer 的任何属性发生更改时运行。

我不能在输入上使用 ngChange。我需要直接订阅模型的更改。原因与我的用例有关,不值得在这里讨论。请相信我,这不是一个选择。

这可能吗?我在谷歌上搜索了很多,但我已经干了。

【问题讨论】:

  • 使用响应式表单,并使用 FormGroup 公开的 valueChanges observable。
  • 这些输入是什么形式的?如果他们不是,他们应该是。如果他们订阅了表单的更改。
  • 表单的状态和对象的状态是一致的。您无法听到对象的更改。但是由于每次表单发生变化,模型也会发生变化,所以监听表单的变化基本上就相当于监听了对象的变化。
  • 您是否尝试将一个简单的 getter/setter 附加到每个 Customer 对象?老实说,我什至不确定这是否可能,但我想我会把它扔在那里。
  • 你可能想通了吗?令人惊讶的是,没有简单的方法可以做到这一点。在我看来,这是一种常见的构建块。

标签: javascript angular typescript


【解决方案1】:

您无法观察对象的变化。 It's not angular 1 这里没有观察者。另一种解决方案是通过 observables。

使用表格

<form #f="ngForm">
  <input type="text" name="firstName" [(ngModel)]="customer.firstName">
  <input type="text" name="favoriteColor" [(ngModel)]="customer.favoriteColor">
</form>

在代码中

@ViewChild('f') f;

ngAfterViewInit() {
  this.f.form.valueChanges.subscribe((change) => {
   console.log(change)
  })
}

【讨论】:

  • Angular4 自动将 novalidate 设置为 ngForms。
  • :-) 直接取自他们的文档angular.io/api/forms/NgForm#form
  • @Mihailo 但已删除 :-) 应该打开文档上的拉取请求
  • 我必须使用 setTimeout() 来确保模型已更新为新值,然后我才能对更改采取行动。
【解决方案2】:

我需要直接订阅模型的更改。

那你需要用ngModelChange监听模型变化

模板:

<input type="text" (ngModelChange)="doSomething($event)" [ngModel]="customer.firstName">

doSomething(event) {
  console.log(event); // logs model value
}

DEMO

【讨论】:

    【解决方案3】:

    Angular 通常使用注入到构造函数KeyValueDiffers 类中。

    对于您的情况,它可能如下所示:

    import { KeyValueChanges, KeyValueDiffer, KeyValueDiffers } from '@angular/core';
    
    export class Customer {
      firstName: string;
      favoriteColor: string;
    }
    
    @Component({
      selector: 'my-app',
      templateUrl: `./app.component.html`
    })
    export class AppComponent {
      private customerDiffer: KeyValueDiffer<string, any>;
      private customer: Customer;
    
      constructor(private differs: KeyValueDiffers) {}
    
      ngOnInit(): void {
        this.customer = new Customer();
        this.customerDiffer = this.differs.find(this.customer).create();
      }
    
      customerChanged(changes: KeyValueChanges<string, any>) {
        console.log('changes');
        /* If you want to see details then use
          changes.forEachRemovedItem((record) => ...);
          changes.forEachAddedItem((record) => ...);
          changes.forEachChangedItem((record) => ...);
        */
      }
    
      ngDoCheck(): void {
          const changes = this.customerDiffer.diff(this.customer);
          if (changes) {
            this.customerChanged(changes);
          }
      }
    }
    

    Stackblitz Example

    另一种选择是在要检查的属性上使用 setter。

    另见

    【讨论】:

    • 这是我发现的唯一可行的方法,但恕我直言,这无论如何都不是 cleansimple 的,我们很糟糕没有提供比必须使用 10 行模式来完成它更好的方法来处理“对象属性已更改,让我知道”的情况。
    • 我必须结合使用这个答案和下面@alexKhymenko 的答案,以确保我的代码注意到所有变化。
    【解决方案4】:

    您可以使用自定义设置器来触发您的回调:

    class Customer {
      private _firstName: string
      get firstName(): string {
        return this._firstName
      }
      set firstName(firstName: string) {
        this.valueChanged(this._firstName, firstName)
        this._firstName = firstName
      }
    
      private _lastName: string
      get lastName(): string {
        return this._lastName
      }
      set lastName(lastName: string) {
        this.valueChanged(this._lastName, lastName)
        this._lastName = lastName
      }
    
      valueChanged: (oldVal, newVal) => void
    
      constructor (valueChanged?: (oldVal, newVal) => void) {
        // return an empty function if no callback was provided in case you don't need
        // one or want to assign it later
        this.valueChanged = valueChanged || (() => {})
      }
    }
    

    然后在创建对象时分配回调:

    this.customer = new Customer((oldVal, newVal) => console.log(oldVal, newVal))
    
    // or
    
    this.customer = new Customer()
    this.customer.valueChanged = (oldVal, newVal) => console.log(oldVal, newVal)
    
    【解决方案5】:

    访问https://github.com/cartant/rxjs-observe。它基于 rxjs 和代理。

    import { observe } from "rxjs-observe";
    
    const instance = { name: "Alice" };
    const { observables, proxy } = observe(instance);
    observables.name.subscribe(value => console.log(name));
    proxy.name = "Bob";
    

    【讨论】:

      【解决方案6】:

      我们的任务是将 Angular 1.x 应用程序转换为 Angular 9。这是一个带有 ESRI 映射的应用程序,因此我们有一些 ESRI 框架提供的简洁工具。 ESRI 的 watchUtils 不仅仅只是观察变化。

      但我错过了 Angular 1 的简单 $watch。此外,我们在应用程序中创建实体和模型,我们可能需要不时观察这些。

      我创建了一个名为 MappedPropertyClass 的抽象类。它使用 Map 来映射类属性,这使我可以轻松实现 toJSON 和其他实用程序功能。

      这个类的另一个 Map 是 _propertyChangeMap: Map;

      我们还有一个名为... $watch 的函数,它接受一个字符串和一个回调函数。

      这个类可以被实体以及组件或服务扩展

      我很乐意分享,需要注意的是您的属性必须如下所示:

      public get foo(): string {
          return this._get("foo");
      }  
      public set foo(value:string) {
          this._set("foo", value);
      }
      
      
      --------------------------------------------------------------------
      import { EventEmitter } from '@angular/core';
      
      export abstract class MappedPropertyClass {
          private _properties: Map<string, any>;
          private _propertyChangeMap: Map<string, EventEmitter<{ newvalue, oldvalue }>>;
      
          protected _set(propertyName: string, propertyValue: any) {
              let oldValue = this._get(propertyName);
              this._properties.set(propertyName, propertyValue);
              this.getPropertyChangeEmitter(propertyName).emit({ newvalue: 
          propertyValue, oldvalue: oldValue });
          }
      
          protected _get(propertyName: string): any {
              if (!this._properties.has(propertyName)) {
                  this._properties.set(propertyName, undefined);
              } 
              return this._properties.get(propertyName);
          }
      
          protected get properties(): Map<string, any> {
              var props = new Map<string, any>();
              for (let key of this._properties.keys()) {
                  props.set(key, this._properties.get(key));
              }
      
              return props;
          }
      
          protected constructor() {
              this._properties = new Map<string, any>();
              this._propertyChangeMap = new Map<string, EventEmitter<{ newvalue: any, 
              oldvalue: any }>>();
          }
      
          private getPropertyChangeEmitter(propertyName: string): EventEmitter<{ 
                               newvalue, oldvalue }> {
              if (!this._propertyChangeMap.has(propertyName)) {
                  this._propertyChangeMap.set(propertyName, new EventEmitter<{ newvalue, 
                  oldvalue }>());
              }
              return this._propertyChangeMap.get(propertyName);
          }
      
          public $watch(propertyName: string, callback: (newvalue, oldvalue) => void): 
          any {
              return this.getPropertyChangeEmitter(propertyName).subscribe((results) => 
              {
                  callback(results.newvalue, results.oldvalue);
              });
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-27
        • 2017-11-06
        相关资源
        最近更新 更多