Что такое угловой эквивалент часов AngularJS $?

213

В AngularJS вы могли указать наблюдателей, чтобы наблюдать изменения в переменных области, используя $watchфункцию $scope. Что эквивалентно наблюдению за изменениями переменных (например, в компонентных переменных) в Angular?

Erwin
источник
Используйте get accessor в машинописи.
jmvtrinidad
посмотрите эту статью, которая объясняет разницу
Макс Корецкий

Ответы:

268

В Angular 2 обнаружение изменений происходит автоматически ... $scope.$watch()и $scope.$digest()RIP

К сожалению, раздел «Обнаружение изменений» в руководстве разработчика еще не написан (в нижней части страницы « Обзор архитектуры» в разделе «Прочие материалы» есть место для заполнения).

Вот мое понимание того, как работает обнаружение изменений:

  • Zone.js «Обезьяна исправляет мир» - он перехватывает все асинхронные API в браузере (при запуске Angular). Вот почему мы можем использовать setTimeout()внутри наших компонентов, а не что-то вроде $timeout... потому что setTimeout()залатана обезьяна.
  • Angular строит и поддерживает дерево «детекторов изменений». Существует один такой детектор изменений (класс) на компонент / директиву. (Вы можете получить доступ к этому объекту путем внедрения ChangeDetectorRef.) Эти детекторы изменений создаются, когда Angular создает компоненты. Они отслеживают состояние всех ваших привязок, для грязной проверки. В некотором смысле они аналогичны автоматическим, $watches()которые Angular 1 будет устанавливать для {{}}привязок к шаблону.
    В отличие от Angular 1, график обнаружения изменений является ориентированным деревом и не может иметь циклов (это делает Angular 2 намного более производительным, как мы увидим ниже).
  • Когда происходит событие (внутри угловой зоны), запускается код, который мы написали (обратный вызов обработчика события). Он может обновлять любые данные, которые ему нужны - модель / состояние общего приложения и / или состояние просмотра компонента.
  • После этого из-за добавленных хуков Zone.js он запускает алгоритм обнаружения изменений Angular. По умолчанию (т. Е. Если вы не используете onPushстратегию обнаружения изменений ни для одного из ваших компонентов), каждый компонент в дереве проверяется один раз (TTL = 1) ... сверху, в порядке глубины. (Хорошо, если вы находитесь в режиме разработки, обнаружение изменений запускается дважды (TTL = 2). Подробнее об этом см. ApplicationRef.tick () .) Он выполняет грязную проверку всех ваших привязок, используя эти объекты детектора изменений.
    • Хуки жизненного цикла называются частью обнаружения изменений.
      Если данные компонента, которые вы хотите просмотреть, являются примитивным входным свойством (String, boolean, number), вы можете реализовать их, ngOnChanges()чтобы получать уведомления об изменениях.
      Если свойство input является ссылочным типом (объект, массив и т. Д.), Но ссылка не изменилась (например, вы добавили элемент в существующий массив), вам нужно будет реализовать ngDoCheck()(см. Этот SO-ответ для получения дополнительной информации). на этом).
      Вам следует только изменять свойства компонента и / или свойства компонентов-потомков (из-за реализации обхода одного дерева - т. Е. Однонаправленного потока данных). Вот плункер, который нарушает это. Трубки с состоянием также могут сбить вас с толку.
  • Для любых найденных изменений связывания компоненты обновляются, а затем обновляется DOM. Обнаружение изменений теперь завершено.
  • Браузер замечает изменения DOM и обновляет экран.

Другие ссылки, чтобы узнать больше:

Марк Райкок
источник
window.addEventListener () не запускает обнаружение при изменении переменной ... это сводит меня с ума, нигде нет ничего такого.
Альберт Джеймс Тедди
@AlbertJamesTeddy, см. hostДокументацию «Host Listeners» в документе DirectiveMetadata API . В нем объясняется, как прослушивать глобальные события изнутри угловой зоны (поэтому обнаружение изменений будет срабатывать по желанию). У этого ответа есть рабочий поршень.
Марк Райкок
эта ссылка была бы полезна ..
рефакторинг
@MarkRajcok, я позволил себе добавить ссылку на мою статью об обнаружении изменений. Надеюсь, ты не возражаешь. Это объясняет в мельчайших деталях, что происходит под капотом.
Макс Корецкий
Что касается plunkr, который нарушает правило однонаправленного потока данных, я хотел бы добавить, что если вы запустите plunkr с помощью enableProdMode (), вы не увидите никаких обновлений в родительском представлении, потому что детектор изменений запускается только один раз.
Mister_L
93

Это поведение теперь является частью жизненного цикла компонента.

Компонент может реализовать метод ngOnChanges в интерфейсе OnChanges, чтобы получить доступ к входным изменениям.

Пример:

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);
  }
}
toskv
источник
77
это верно только для @Input (). если вы хотите отслеживать изменения собственных данных вашего компонента, это не сработает
LanderV
4
Я не мог получить изменения простых переменных (логическое значение, например). Обнаружены только изменения объектов.
mtoloo
Почему нужно добавить массив «входы» в декоратор компонента? Обнаружение изменений будет работать и без этого.
Гил Эпштейн
68

Если в дополнение к автоматической двусторонней привязке вы хотите вызывать функцию при изменении значения, вы можете нарушить синтаксис двусторонней привязки для более подробной версии.

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

supermikko
источник
В последнем примере вы хотели удалить [] вокруг ngModel?
Евгений Кулабухов
16

Вы можете использовать getter functionили get accessorдействовать как часы на угловой 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);
    }
}
jmvtrinidad
источник
12

Вот еще один подход, использующий функции получения и установки для модели.

@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;
  }
}
Выбрать
источник
4
Этот предмет безумен. У меня есть объект со многими свойствами, привязанными к сложной форме. Я не хочу добавлять (change)обработчики для каждого из них; Я не хочу добавлять get|setss к каждому свойству в моей модели; это не поможет добавить get|setдля this.object; ngOnChanges() обнаруживает только изменения в @Inputс . Святая манна! Что они с нами сделали ??? Верните нам какие-то глубокие часы!
Коди
6

Если вы хотите сделать его двухсторонним связыванием, вы можете использовать его [(yourVar)], но вам нужно реализовать yourVarChangeсобытие и вызывать его каждый раз, когда меняется ваша переменная.

Как то так, чтобы отслеживать смену героя

@Output() heroChange = new EventEmitter();

а потом, когда ваш герой переоденется, позвоните this.heroChange.emit(this.hero);

[(hero)]связывании остальное сделает за вас

посмотрите пример здесь:

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

Моги Элдин
источник
2

Это не дает прямого ответа на вопрос, но я несколько раз сталкивался с этим вопросом переполнения стека, чтобы решить что-то, для чего я бы использовал $ watch в angularJs. Я использовал другой подход, чем описанный в текущих ответах, и хочу поделиться им на случай, если кто-то сочтет это полезным.

Техника, которую я использую для достижения чего-то похожего, $watchзаключается в использовании BehaviorSubject( подробнее о теме здесь ) в службе Angular и разрешении моим компонентам подписываться на нее, чтобы получать (наблюдать) изменения. Это похоже на $watchв angularJs, но требует дополнительной настройки и понимания.

В моем компоненте:

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

Вот демо на Stackblitz

Джон
источник