X Tutup
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 69 additions & 53 deletions packages/core/src/render3/instructions/change_detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,37 @@ export function detectChanges(component: {}): void {
detectChangesInternal(view[TVIEW], view, component);
}

/**
* Different modes of traversing the logical view tree during change detection.
*
*
* The change detection traversal algorithm switches between these modes based on various
* conditions.
*/
const enum ChangeDetectionMode {
/**
* In `Global` mode, `Dirty` and `CheckAlways` views are refreshed as well as views with the
* `RefreshTransplantedView` flag.
*/
Global,
/**
* In `Targeted` mode, only views with the `RefreshTransplantedView`
* flag are refreshed.
*/
Targeted,
/**
* Used when refreshing a view to force a refresh of its embedded views. This mode
* refreshes views without taking into account their LView flags, i.e. non-dirty OnPush components
* will be refreshed in this mode.
*
* TODO: we should work to remove this mode. It's used in `refreshView` because that's how the
* code worked before introducing ChangeDetectionMode. Instead, it should pass `Global` to the
* `detectChangesInEmbeddedViews`. We should aim to fix this by v17 or, at the very least, prevent
* this flag from affecting signal views not specifically marked for refresh (currently, this flag
* would _also_ force signal views to be refreshed).
*/
BugToForceRefreshAndIgnoreViewFlags
}

/**
* Processes a view in update mode. This includes a number of steps in a specific order:
Expand Down Expand Up @@ -122,7 +153,7 @@ export function refreshView<T>(
// insertion points. This is needed to avoid the situation where the template is defined in this
// `LView` but its declaration appears after the insertion component.
markTransplantedViewsForRefresh(lView);
refreshEmbeddedViews(lView);
detectChangesInEmbeddedViews(lView, ChangeDetectionMode.BugToForceRefreshAndIgnoreViewFlags);

// Content query results must be refreshed before content hooks are called.
if (tView.contentQueries !== null) {
Expand Down Expand Up @@ -152,7 +183,7 @@ export function refreshView<T>(
// Refresh child component views.
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
detectChangesInChildComponents(lView, components, ChangeDetectionMode.Global);
}

// View queries must execute after refreshing child components because a template in this view
Expand Down Expand Up @@ -208,16 +239,12 @@ export function refreshView<T>(
* Goes over embedded views (ones created through ViewContainerRef APIs) and refreshes
* them by executing an associated template function.
*/
export function refreshEmbeddedViews(lView: LView) {
function detectChangesInEmbeddedViews(lView: LView, mode: ChangeDetectionMode) {
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
lContainer = getNextLContainer(lContainer)) {
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
const embeddedLView = lContainer[i];
const embeddedTView = embeddedLView[TVIEW];
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
if (viewAttachedToChangeDetector(embeddedLView)) {
refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
}
detectChangesInView(embeddedLView, mode);
}
}
}
Expand All @@ -227,7 +254,7 @@ export function refreshEmbeddedViews(lView: LView) {
*
* @param lView The `LView` that may have transplanted views.
*/
export function markTransplantedViewsForRefresh(lView: LView) {
function markTransplantedViewsForRefresh(lView: LView) {
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
lContainer = getNextLContainer(lContainer)) {
if (!lContainer[HAS_TRANSPLANTED_VIEWS]) continue;
Expand All @@ -244,68 +271,57 @@ export function markTransplantedViewsForRefresh(lView: LView) {
}

/**
* Refreshes components by entering the component view and processing its bindings, queries, etc.
* Detects changes in a component by entering the component view and processing its bindings,
* queries, etc. if it is CheckAlways, OnPush and Dirty, etc.
*
* @param componentHostIdx Element index in LView[] (adjusted for HEADER_OFFSET)
*/
function refreshComponent(hostLView: LView, componentHostIdx: number): void {
function detectChangesInComponent(
hostLView: LView, componentHostIdx: number, mode: ChangeDetectionMode): void {
ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode');
const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
if (viewAttachedToChangeDetector(componentView)) {
const tView = componentView[TVIEW];
if (componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
refreshView(tView, componentView, tView.template, componentView[CONTEXT]);
} else if (componentView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
refreshContainsDirtyView(componentView);
}
}
detectChangesInView(componentView, mode);
}

/**
* Refreshes all transplanted views marked with `LViewFlags.RefreshTransplantedView` that are
* children or descendants of the given lView.
* Visits a view as part of change detection traversal.
*
* - If the view is detached, no additional traversal happens.
*
* The view is refreshed if:
* - If the view is CheckAlways or Dirty and ChangeDetectionMode is `Global`
* - If the view has the `RefreshTransplantedView` flag
*
* The view is not refreshed, but descendants are traversed in `ChangeDetectionMode.Targeted` if the
* view has a non-zero TRANSPLANTED_VIEWS_TO_REFRESH counter.
*
* @param lView The lView which contains descendant transplanted views that need to be refreshed.
*/
function refreshContainsDirtyView(lView: LView) {
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
lContainer = getNextLContainer(lContainer)) {
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
const embeddedLView = lContainer[i];
if (viewAttachedToChangeDetector(embeddedLView)) {
if (embeddedLView[FLAGS] & LViewFlags.RefreshView) {
const embeddedTView = embeddedLView[TVIEW];
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
refreshView(
embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);

} else if (embeddedLView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
refreshContainsDirtyView(embeddedLView);
}
}
}
function detectChangesInView(lView: LView, mode: ChangeDetectionMode) {
if (!viewAttachedToChangeDetector(lView)) {
return;
}

const tView = lView[TVIEW];
// Refresh child component views.
const components = tView.components;
if (components !== null) {
for (let i = 0; i < components.length; i++) {
const componentView = getComponentLViewByIndex(components[i], lView);
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
if (viewAttachedToChangeDetector(componentView) &&
componentView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
refreshContainsDirtyView(componentView);
}
if ((lView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty) &&
mode === ChangeDetectionMode.Global) ||
lView[FLAGS] & LViewFlags.RefreshView ||
mode === ChangeDetectionMode.BugToForceRefreshAndIgnoreViewFlags) {
refreshView(tView, lView, tView.template, lView[CONTEXT]);
} else if (lView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
detectChangesInEmbeddedViews(lView, ChangeDetectionMode.Targeted);

const tView = lView[TVIEW];
const components = tView.components;
if (components !== null) {
detectChangesInChildComponents(lView, components, ChangeDetectionMode.Targeted);
}
}
}

/** Refreshes child components in the current view (update mode). */
export function refreshChildComponents(hostLView: LView, components: number[]): void {
function detectChangesInChildComponents(
hostLView: LView, components: number[], mode: ChangeDetectionMode): void {
for (let i = 0; i < components.length; i++) {
refreshComponent(hostLView, components[i]);
detectChangesInComponent(hostLView, components[i], mode);
}
}
21 changes: 12 additions & 9 deletions packages/core/test/bundling/animations/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,18 @@
{
"name": "detachMovedView"
},
{
"name": "detectChangesInChildComponents"
},
{
"name": "detectChangesInComponent"
},
{
"name": "detectChangesInEmbeddedViews"
},
{
"name": "detectChangesInView"
},
{
"name": "detectChangesInternal"
},
Expand Down Expand Up @@ -1355,12 +1367,6 @@
{
"name": "refCount"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContainsDirtyView"
},
{
"name": "refreshContentQueries"
},
Expand Down Expand Up @@ -1526,9 +1532,6 @@
{
"name": "urlParsingNode"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "visitDslNode"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,18 @@
{
"name": "detachMovedView"
},
{
"name": "detectChangesInChildComponents"
},
{
"name": "detectChangesInComponent"
},
{
"name": "detectChangesInEmbeddedViews"
},
{
"name": "detectChangesInView"
},
{
"name": "detectChangesInternal"
},
Expand Down Expand Up @@ -1070,12 +1082,6 @@
{
"name": "refCount"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContainsDirtyView"
},
{
"name": "refreshContentQueries"
},
Expand Down Expand Up @@ -1208,9 +1214,6 @@
{
"name": "urlParsingNode"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "walkProviderTree"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,18 @@
{
"name": "detachView"
},
{
"name": "detectChangesInChildComponents"
},
{
"name": "detectChangesInComponent"
},
{
"name": "detectChangesInEmbeddedViews"
},
{
"name": "detectChangesInView"
},
{
"name": "detectChangesInternal"
},
Expand Down Expand Up @@ -1505,12 +1517,6 @@
{
"name": "refCount"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContainsDirtyView"
},
{
"name": "refreshContentQueries"
},
Expand Down Expand Up @@ -1697,9 +1703,6 @@
{
"name": "urlParsingNode"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "walkProviderTree"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,18 @@
{
"name": "detachView"
},
{
"name": "detectChangesInChildComponents"
},
{
"name": "detectChangesInComponent"
},
{
"name": "detectChangesInEmbeddedViews"
},
{
"name": "detectChangesInView"
},
{
"name": "detectChangesInternal"
},
Expand Down Expand Up @@ -1469,12 +1481,6 @@
{
"name": "refCount"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContainsDirtyView"
},
{
"name": "refreshContentQueries"
},
Expand Down Expand Up @@ -1673,9 +1679,6 @@
{
"name": "urlParsingNode"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "walkProviderTree"
},
Expand Down
21 changes: 12 additions & 9 deletions packages/core/test/bundling/hello_world/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,18 @@
{
"name": "detachMovedView"
},
{
"name": "detectChangesInChildComponents"
},
{
"name": "detectChangesInComponent"
},
{
"name": "detectChangesInEmbeddedViews"
},
{
"name": "detectChangesInView"
},
{
"name": "detectChangesInternal"
},
Expand Down Expand Up @@ -839,12 +851,6 @@
{
"name": "refCount"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContainsDirtyView"
},
{
"name": "refreshContentQueries"
},
Expand Down Expand Up @@ -953,9 +959,6 @@
{
"name": "urlParsingNode"
},
{
"name": "viewAttachedToChangeDetector"
},
{
"name": "walkProviderTree"
},
Expand Down
Loading
X Tutup