Angular 中有没有与 AngularJS 的 $watch 相对应的功能?

225
在AngularJS中,您可以使用$scope的$watch函数指定监视器以观察作用域变量的更改。那么,在Angular中观察变量的变化(例如组件变量)的等效方法是什么?

在TypeScript中使用get访问器。 - jmvtrinidad
请查看此文章,其中解释了Angular的Digest在新版本中的区别:https://hackernoon.com/angulars-digest-is-reborn-in-the-newer-version-of-angular-718a961ebd3e - Max Koretskyi
7个回答

278
在Angular 2中,变更检测是自动的... $scope.$watch()$scope.$digest() 已过时。
不幸的是,开发指南中的变更检测部分尚未编写(在架构概述页面底部的“其他内容”部分有一个占位符)。
以下是我对变更检测工作原理的理解:
  • Zone.js "猴子补丁了全球"——它拦截了浏览器中所有异步API(当Angular运行时)。这就是为什么我们可以在组件内部使用setTimeout()而不是像$timeout这样的东西,因为setTimeout()是被猴子补丁过的。
  • Angular构建并维护一个“变更检测器”的树。每个组件/指令都有一个这样的变更检测器(类)。(您可以通过注入ChangeDetectorRef来访问此对象。)这些变更检测器是在Angular创建组件时创建的。它们跟踪所有绑定的状态,以进行脏检查。从某种意义上说,它们类似于Angular 1为{{}}模板绑定设置的自动$watches()
    与Angular 1不同,变更检测图是一个有向树,不能有循环(这使得Angular 2更加高效,如下所示)。
  • 当事件触发时(在Angular区域内),我们编写的代码(事件处理程序回调)运行。它可以更新任何数据——共享应用程序模型/状态和/或组件的视图状态。
  • 之后,由于Zone.js添加的钩子,它会运行Angular的变更检测算法。默认情况下(即,如果您没有在任何组件上使用onPush变更检测策略),每个组件在树中被检查一次(TTL=1)……从顶部按深度优先顺序进行。(好吧,如果您处于dev模式,则变更检测运行两次(TTL=2)。有关此更多信息,请参见ApplicationRef.tick()。)它对所有绑定执行脏检查,使用那些变更检测器对象。
    • 生命周期钩子作为变更检测的一部分被调用。
      如果要监视的组件数据是基元输入属性(字符串、布尔值、数字),则可以实现ngOnChanges()来通知更改。
      如果输入属性是引用类型(对象、数组等),但引用没有更改(例如,您向现有数组添加了一个项目),则需要实现ngDoCheck()(有关详细信息,请参见this SO answer)。
      您应该只更改组件的属性和/或后代组件的属性(由于单树遍历实现——即单向数据流)。这里有一个a plunker违反了这个原则。状态管道也可能让你困扰
  • 对于找到的任何绑定更改,组件都会更新,然后DOM会更新。变更检测现在已经完成。
  • 浏览器注意到DOM更改并更新屏幕。

更多学习资料:


window.addEventListener()在变量更改时不会触发检测...这让我疯了,任何地方都没有相关信息。 - Albert James Teddy
@AlbertJamesTeddy,请查看 DirectiveMetadata API 文档 中的 host 和 "Host Listeners" 部分。它解释了如何从 Angular 区域内部监听全局事件(以便触发所需的变更检测)。这个答案 有一个可工作的 plunker。 - Mark Rajcok
这个链接会很有帮助:https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html - refactor
@MarkRajcok,我冒昧地在我的文章中添加了关于变更检测的参考。希望你不介意。它详细解释了底层发生的事情。 - Max Koretskyi
关于违反单向数据流规则的plunkr,我想补充一下,如果你使用enableProdMode()运行plunkr,你将看不到父视图中的任何更新,因为变更检测器只运行一次。 - Mister_L
由于zone.js会猴子补丁异步函数,如XMLHttpRequest(),这是否意味着甚至使用HTTP请求的外部库也会触发Angular的生命周期?虽然听起来是这样的,但你可以指定“当事件在Angular区域内触发” 。另外,根据zone.js文档,似乎在任何给定时刻可以有多个区域。 - c1moore

98

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

一个组件可以在 OnChanges 接口中实现 ngOnChanges 方法以获取对输入变化的访问权限。

示例:

import {Component, Input, OnChanges} from 'angular2/core';


@Component({
  selector: 'hero-comp',
  templateUrl: 'app/components/hero-comp/hero-comp.html',
  styleUrls: ['app/components/hero-comp/hero-comp.css'],
  providers: [],
  directives: [],

  pipes: [],
  inputs:['hero', 'real']
})
export class HeroComp implements OnChanges{
  @Input() hero:Hero;
  @Input() real:string;
  constructor() {
  }
  ngOnChanges(changes) {
      console.log(changes);
  }
}

85
这只适用于 @Input()。如果你想追踪组件自身数据的变化,这种方法不起作用。 - LanderV
4
我无法获取简单变量(例如布尔变量)的更改。只有对象的更改才能被检测到。 - mtoloo
为什么需要在组件的装饰器中添加一个“inputs”数组?即使没有它,变更检测也可以正常工作。 - Gil Epshtain

73
如果除了自动的双向绑定,您想在值改变时调用一个函数,可以打破双向绑定快捷语法,使用更冗长的版本。 <input [(ngModel)]="yourVar"></input> 是以下缩写形式: <input [ngModel]="yourVar" (ngModelChange)="yourVar=$event"></input> (参见例如http://victorsavkin.com/post/119943127151/angular-2-template-syntax
您可以这样做: <input [(ngModel)]="yourVar" (ngModelChange)="changedExtraHandler($event)"></input>

在最后一个例子中,您是否意味着删除[]周围的ngModel? - Eugene Kulabuhov
对我来说,这是最好的答案,特别是关于(ngModelChange)的最后一行。每当检测到[(ngModel)]中的更改时,就触发该函数。太棒了!这就是我大部分$watch从旧的AngularJS转换为新的Angular应用程序的方式。 - Jason

17
你可以使用getter函数获取器在Angular 2上充当监视器。
请参见演示此处
import {Component} from 'angular2/core';

@Component({
  // Declare the tag name in index.html to where the component attaches
  selector: 'hello-world',

  // Location of the template for this component
  template: `
  <button (click)="OnPushArray1()">Push 1</button>
  <div>
    I'm array 1 {{ array1 | json }}
  </div>
  <button (click)="OnPushArray2()">Push 2</button>
  <div>
    I'm array 2 {{ array2 | json }}
  </div>
  I'm concatenated {{ concatenatedArray | json }}
  <div>
    I'm length of two arrays {{ arrayLength | json }}
  </div>`
})
export class HelloWorld {
    array1: any[] = [];
    array2: any[] = [];

    get concatenatedArray(): any[] {
      return this.array1.concat(this.array2);
    }

    get arrayLength(): number {
      return this.concatenatedArray.length;
    }

    OnPushArray1() {
        this.array1.push(this.array1.length);
    }

    OnPushArray2() {
        this.array2.push(this.array2.length);
    }
}

12

以下是使用getter和setter函数处理模型的另一种方法。

@Component({
  selector: 'input-language',
  template: `
  …
  <input 
    type="text" 
    placeholder="Language" 
    [(ngModel)]="query" 
  />
  `,
})
export class InputLanguageComponent {

  set query(value) {
    this._query = value;
    console.log('query set to :', value)
  }

  get query() {
    return this._query;
  }
}

8
这个主题太疯狂了。我有一个与复杂表单相关联的具有“许多”属性的对象。我不想在每个属性上都添加(change)处理程序;我也不想在我的模型的每个属性中添加get|set; 为this.object添加get|set也没有用; ngOnChanges()仅检测@Input的更改。天啊!他们对我们做了什么?给我们一个类似于深度监视的东西! - Cody

8
如果您想实现双向绑定,可以使用 [(yourVar)],但是您需要实现yourVarChange事件并在每次更改变量时调用它。
例如,像这样跟踪英雄的更改:
@Output() heroChange = new EventEmitter();

当你的英雄被改变后,调用this.heroChange.emit(this.hero);

[(hero)]绑定将为您完成其余工作。

请参见此处的示例:

http://plnkr.co/edit/efOGIJ0POh1XQeRZctSx?p=preview


6

这并没有直接回答问题,但我在不同的场合都会着陆在这个Stack Overflow问题中,以解决我在AngularJS中使用$watch的问题。最终,我采用了另一种方法来实现与$watch类似的效果,并希望在有人发现它有用时分享出来。

我使用的技巧是,在Angular服务中使用BehaviorSubject(更多内容请参见此处),让我的组件订阅它以获取(监视)变化。这类似于在angularJs中使用$watch,但需要更多设置和理解。

在我的组件中:

export class HelloComponent {
  name: string;
  // inject our service, which holds the object we want to watch.
  constructor(private helloService: HelloService){
    // Here I am "watching" for changes by subscribing
    this.helloService.getGreeting().subscribe( greeting => {
      this.name = greeting.value;
    });
  }
}

在我的服务中

export class HelloService {
  private helloSubject = new BehaviorSubject<{value: string}>({value: 'hello'});
  constructor(){}
  // similar to using $watch, in order to get updates of our object 
  getGreeting(): Observable<{value:string}> {
    return this.helloSubject;
  }
  // Each time this method is called, each subscriber will receive the updated greeting.
  setGreeting(greeting: string) {
    this.helloSubject.next({value: greeting});
  }
}

这是一个关于IT技术的演示,在Stackblitz上进行。

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