【问题标题】:How to watch on complex object in Angular 2 like we did in Angular 1 using $watch如何在 Angular 2 中观察复杂对象,就像我们在 Angular 1 中使用 $watch 所做的一样
【发布时间】:2016-10-19 17:12:25
【问题描述】:

我们能够在复杂对象上应用$watch,如何在 Angular 2 中进行类似操作。

角度 1

$scope.data = {name : "somvalue"}
$scope.$watch('data.name', function(newValue, oldValue) {
    scope.counter = scope.counter + 1;
});

Angular 2

export class MyData{
   name: string;
} 

export class MyComponent implements OnInit {
   @Input() data: MyData;

   constructor(private ds: MyService){
      this.data = ds.data;
   }

   // $watch('data.name', function(newValue, oldValue) {
   //   scope.counter = scope.counter + 1;
   // });
}

现在如果data.name服务发生变化,如何观察组件本身的变化,请注意数据不是可观察的,它只是一个常规对象。

更新

Please see Plunk for an example

提前致谢!!

【问题讨论】:

标签: angularjs angular typescript1.8


【解决方案1】:

如果您想进行更多自定义并执行更多手动操作,这是DoCheck 的替代解决方案。当然,根据Günter Zöchbauer answerObservable 的一种解决方案。

我使用装饰器告诉 TS 必须检测到属性,所以让我们开始吧。

export class ChangeDetection {
  private emitedChangeDetector: ChangeDetection;

  private _value;
  get value() {
    return this._value;
  }

  /** @warning Don't use it */
  __action: (value: any) => void;

  /** @warning Don't use it */
  __change(value: any) {
    this._value = value;
    this.__action && this.__action(value);
    this.emitedChangeDetector?.__change &&
      this.emitedChangeDetector.__change(this.emitedChangeDetector.value);
  }

  onChange<T = any>(action: (value: T) => void) {
    this.__action = action;

    return this;
  }

  emit(extendedObserver: ChangeDetection) {
    this.emitedChangeDetector = extendedObserver;
  }
}

// For manage list of changes
export class ChangeDetectionList {
  changeDetectors: ChangeDetection[];

  constructor(...changeDetectors: ChangeDetection[]) {
    this.changeDetectors = changeDetectors;
  }

  onChange(callback: (data: any) => void): ChangeDetectionList {
    this.changeDetectors.forEach((a: ChangeDetection) => a.onChange(callback));

    return this;
  }

  emit(changeDetector: ChangeDetection): ChangeDetection {
    this.changeDetectors.forEach((a: ChangeDetection) =>
      a.emit(changeDetector)
    );

    return changeDetector;
  }
}

/**
 * @usageNotes{
 * ```typescript
 * @ChangeDetector()
 * data : string
 * ```
 * Gives you a ChangeDetection object with the name of data$ that fires "onChange()" function when "data" is changed
 */
export function ChangeDetector(suffix: string = "$") {
  return function (prototype: any, key: string | symbol) {
    const changeDetectorName: string = `${key.toString()}${suffix}`;
    if (!prototype[changeDetectorName]) {
      Object.defineProperty(prototype, changeDetectorName, {
        value: new ChangeDetection(),
      });

      const changeDetectorObject = prototype[changeDetectorName] as ChangeDetection;
      Object.defineProperty(prototype, key, {
        set(value: any) {
          Object.defineProperty(this, key, {
            get() {
              return changeDetectorObject.value;
            },
            set(v: any) {
              changeDetectorObject.__change(v);
            },
            enumerable: true,
          });
          this[key] = value;
        },
        enumerable: true,
        configurable: true,
      });
    }
  };
}

现在,它很容易使用。只需将 @ChangeDetector() 装饰器放在您要检测的每个属性上,或定义 changeDetector 属性以在属性更改时引发 onChange 事件。

这里有一个例子告诉你如何使用它。

export class PersonModel {
  @ChangeDetector()
  changeDetector: number;
  changeDetector$: ChangeDetection; // It's just for intellisense, you can ignore it.

  firstName: string;
  lastName: string;
  age: number;

  constructor() {
    this.age = 34;
    this.changeDetector = 0;
  }
}

export class ProfileModel {
  @ChangeDetector('Listener')
  person: PersonModel;
  personListener: ChangeDetection; // It's just for intellisense, you can ignore it.

  constructor() {
    this.person = new PersonModel();
  }
}

export class ProfileService {
  profile: ProfileModel;

  constructor() {
    this.profile = new ProfileModel();
    // calls 'profileChanged()' when 'person' is changed
    this.profile.personListener.onChange((_) => this.profileChanged());
  }

  setProfile() {
    Object.assign(this.profile.person, { firstName: 'Pedram', lastName: 'Ahmadpour' });
    this.profile.person.changeDetector++;
  }

  private profileChanged() {
    console.log('profile is', JSON.stringify(this.profile));
  }
}

export class ProfileComponent {
  constructor(private profileService: ProfileService) {
    // Now, profileService.profile listen to its own child changes
    this.profileService.profile.person.changeDetector$.emit(profileService.profile.personListener);
    this.profileService.setProfile();
  }

  happyBirthday() {
    this.profileService.profile.person.age = 35;
    console.log('Happy birthday');
    this.profileService.profile.person.changeDetector++;
  }
}

或者在真正的 Angular 项目中:

  @Input()
  @ChangeDetector()
  data: ProfileModel;
  data$: ChangeDetection;

对于测试:

const profileService = new ProfileService();
const profileComponent = new ProfileComponent(profileService);
profileComponent.happyBirthday();

【讨论】:

    【解决方案2】:

    实现组件生命周期钩子“ngOnChanges”:

    import { OnChanges } from '@angular/core';
    
    @Component({
        selector: 'child',
        template: `
            <h2>Child component</h2>
            {{ person }}
        `
    })
    class ChildComponent implements OnChanges {
        @Input() person: string;
    
        ngOnChanges(changes: {[ propName: string]: SimpleChange}) {
            console.log('Change detected:', changes[person].currentValue);
        }
    
    }
    

    更新

    我找到了一个可能的解决方法。实现 DoCheck 钩子而不是 OnChanges。

    ngDoCheck() {
       if(!this.inputSettings.equals(this.previousInputSettings)) {
           // inputSettings changed
           // some logic here to react to the change
           this.previousInputSettings = this.inputSettings;
       }
    }
    

    请记住,doCheck 运行多次,如果使用不当可能会导致性能问题。

    【讨论】:

    • 如果对象绑定在 UI 上,则更改将反映,但仅在属性更改时不会触发 ngOnChanges,我在问题中添加了一个 Plunk,它解释了场景,如果您更新属性如果您替换将触发的整个对象,ngOnChanges 不会触发,您可以在控制台日志中看到它并单击相应的按钮。
    • 好的!您需要对该属性使用直接引用: 请参阅:victorsavkin.com/post/133936129316/…
    • 是的,我知道正如上面评论中提到的,我有一个场景,我想观察的属性不直接绑定在 UI 上,而是负责 UI 的更改。
    【解决方案3】:

    Angular 会检查属性,即使是在模板中绑定的对象的深处。

    对于复杂对象,首选选项是使用Observable 主动通知 Angular2 有关更改。

    您还可以通过实现DoCheck 来使用自定义更改检测

    【讨论】:

    • 你有一些参考代码如何创建 observable 来实现这一点吗?我知道 API 调用如何产生 observable,但是如何对普通的类对象做同样的事情呢?我创建了一个 Plunk 来解释这个场景,Observable 可以适应这种情况吗?
    • 补充一下,我有一个场景,我要观察的属性不是直接绑定在UI上,而是会负责UI的变化。
    • 您可以强制订阅 observables 并在事件发出时进行更新。 Observable 是关于推送以主动获取有关更改的通知,而不是轮询。 Angular2 在减少变更检测引起的负载方面做得很好,但 observables 可以进一步减少这种情况。
    【解决方案4】:

    一般来说,你的组件会监听它所有的对象突变,除非你使用不同的变化检测策略。

    import { OnChanges } from '@angular/core';
    
    @Component({
        selector: 'child',
        template: `
            <h2>Child component</h2>
            {{ person }}
        `,
       changeDetection:ChangeDetectionStrategy.OnPush/Default/CheckOnce/Always
    })
    class ChildComponent implements OnChanges {
        @Input() person: string;
    
        ngOnChanges(changes: {[ propName: string]: SimpleChange}) {
            console.log('Change detected:', changes[person].currentValue);
        }
    

    这个:

       changeDetection:ChangeDetectionStrategy.OnPush/Default/CheckOnce/Always
    

    将定义组件在其中一个输入更新时的行为方式。

    最重要的是 OnPush 和 Default 。

    OnPush 表示只有在整个对象被新对象替换时才更新组件,而 Default 表示如果任何嵌套值已更新(变异),则更新我。

    而且,如果您不在组件中使用该对象,Angular 将忽略并且不会更新视图(为什么会这样)。

    然后您可以轻松地挂钩到 ngOnChange 生命周期挂钩,并按照其他答案的建议获取更新。

    【讨论】:

    • CheckOnceDefault 是保持公开状态的唯一选择。其他选项仅供 Angular2 内部使用,不应由开发人员使用,最终将转移到私有枚举。
    • 如果对象绑定在 UI 上,则更改将反映,但仅在属性更改时不会触发 ngOnChanges,我在问题中添加了一个 Plunk,它解释了场景,如果您更新属性如果您替换将触发的整个对象,ngOnChanges 不会触发,您可以在控制台日志中看到它并单击相应的按钮。
    猜你喜欢
    • 2017-12-17
    • 2017-06-07
    • 1970-01-01
    • 2020-06-03
    • 2020-01-19
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 2018-05-30
    相关资源
    最近更新 更多