Помогите пожалуйста понять почему в шаблоне срабатывает счётчик приходящих в дочерний компонент переменных?
LIVE DEMO
Как видите, при вводе в поле input нового значения оно отправляется в дочерний компонент как Input-свойство. При это в дочернем компоненте каждый раз срабатывает инкремент счётчика.
component:
@Input()
public msg = '';
private _count = 0;
public renderCounter() {
return ++this._count;
}
tpl:
<h3 *ngIf="msg">{{msg}}</h3>
<span>Render counter: {{renderCounter()}}</span>
Мне не понятно почему происходит инкремент счётчика. Правильно ли я понимаю, что когда получением дочерним компонентом Input-значения засятавляет компонент полностью перерисовывать свой шаблон?
А вы думали, что в Angular есть частичный рендеринг? Нет, OnPush
- это всего лишь ограничение на то, когда будет запущен следующий механизм обнаружения изменений на представлении (view), которое маркировано, как OnPush
.
Ссылка на msg
не меняется просто так, это происходит благодаря обработчику событий input
, который вешает директива ngModel
. Так как Angular использует тотальный контроль над event loop'ом браузера - на любое асинхронное событие Angular запускает механизм обнаружения изменений, который рекурсивно пробегается по всему графу, используя алгоритм "поиск в глубину".
Алгоритм обнаружения изменений на какой-то вьюхе довольно таки прост абстрактно, но очень сложно реализован под капотом. Возьмем ваш компонент onpush-value
. Когда происходит асинхронное событие - зона оповещает Angular о том, что "пожалуйста запусти механизм обнаружения изменений". Все что там происходит это:
tick() {
if (this._runningTick) {
throw new Error('ApplicationRef.tick is called recursively');
}
const scope = ApplicationRef._tickScope();
try {
this._runningTick = true;
this._views.forEach((view) => view.detectChanges());
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.checkNoChanges());
}
}
catch (e) {
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e));
}
finally {
this._runningTick = false;
wtfLeave(scope);
}
}
В цикле проходит по всем вьюхам и вызывает метод detectChanges
. detectChanges
- это метод, который находится в прототипе класса ViewRef_
(ссылка на вьюху). Что делает этот метод?
try {
Services.checkAndUpdateView(this._view);
}
finally {
if (fs.end) {
fs.end();
}
}
checkAndUpdateView
в свою очередь вызывает синхронно большое количество других функций, все это происходит рекурсивно, по той причине, что вьюха может иметь ссылки на потомков, потомки в свою очередь на потомки, поэтому в цикле while
потомок становится родителем.
Все, что делают те функции (вы можете открыть сурсы Angular и ознакомиться с ними детальнее) - это проверяют входные параметры (input
), вызывают соответствующие методы жизненных циклов и перерисовывают компонент (благодаря классу Renderer
). Но большинство этих функций возвращают boolean
значение, поэтому Angular не просто проверяет входные свойства, Angular так же проверяет - маркирована ли вьюха, как OnPush
:
if (compView.def.flags & 2 /* OnPush */) {
compView.state |= 8 /* ChecksEnabled */;
}
Если же вьюха маркирована как OnPush
и ссылки на входные параметры не изменились, то дело до checkAndUpdateNode
просто не дойдет, поэтому вьюха просто скипнется, если же поменялась ссылка на любой входной параметр, то Angular отдает управление функциям debugCheckAndUpdateNode
(в дев моде) либо prodCheckAndUpdateNode
(в продакшн моде), которые перерисовывают полностью всю вьюху.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Допустим есть такая таблица __test_if_else
Есть две таблицы в одной БД group и student, задача стоит вытащить группы в которых нет студентов, в таблице student есть столбец groupID который обозначает...