angular
April 11

Что произойдет, если передать на вход @Input объект при включенной стратегии OnPush

На одном из собеседований мне задали вопрос — есть компонент FooComponent, с включенной стратегией обнаружения изменения OnPush и в него передаётся объект. Что происходит в этот момент под капотом? Создает ли Angular новый объект, вынуждая FooComponent пройти проверку? Попробуем разобраться.

@Component({
  template: `
    <app-foo [config]="{ position: 'top' }"></app-foo>
  `
})
export class AppComponent {
}
@Component({
  selector: 'app-foo',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FooComponent {
  @Input() config: Config;
}

В нашем случае, когда Angular компилирует шаблон, он замечает, что объект статический и не использует каких-либо привязок. Поэтому Angular создает его один раз, кэширует и всегда возвращает ту же ссылку при последующих проверках.

Если мы посмотрим на скомпилированный код, мы увидим, что Angular использует функцию pureFunction0, которая делает то, что мы описали выше:

const _c0 = function() {
  return {
    position: "top"
  };
};

export class AppComponent {}

AppComponent.ɵcmp = ɵɵdefineComponent({
  type: AppComponent,
  template: function AppComponent_Template(rf, ctx) {
    if (rf & 2) {
      ɵɵproperty("config", ɵɵpureFunction0(1, _c0));
    }
  },
  directives: [i1.FooComponent]
});

Перейдем к следующему примеру, где значение одного из свойств объекта не является статическим:

@Component({
  template: `
    <app-foo [config]="{ position: position }"></app-foo>
   `
})
export class AppComponent {
  position = 'top';

  change() {
    this.position = 'bottom';   
  }
}

В этом случае для Angular не важен сам объект. Когда компилятор обнаруживает привязку, он сохраняет ее значение в структуре данных LView. В коде имеется одна привязка, поэтому Angular будет использовать версию pureFunction1:

const _c0 = function() {
  return {
    position: "top"
  };
};

export class AppComponent {}

AppComponent.ɵcmp = ɵɵdefineComponent({
  type: AppComponent,
  template: function AppComponent_Template(rf, ctx) {
    if (rf & 2) {
      ɵɵproperty("config", ɵɵpureFunction1(1, _c0, ctx.position));
    }
  },
  directives: [i1.FooComponent]
});

Эта функция вернет кэшированную ссылку на объект, если текущее значение position такое же, как и кэшированное. В противном случае она вернет новый объект, содержащий обновленное значение свойства.

Результирующий объект будет передан функции ɵɵproperty, которая использует функцию Object.is для проверки необходимости обновления свойства.

В заключение, единственным недостатком при использовании таких объектов вместо свойства компонента является то, что компилятор будет гсоздавать и выполнять дополнительный код.