Skip to content

Commit

Permalink
fix(core): Newly created and attached views should be refreshed durin…
Browse files Browse the repository at this point in the history
…g change detection

When a view is created and attached, we should ensure that it is reached
and refreshed during change detection. This can happen if the view is
created and attached outside a change run or when it is created and
attached after its insertion view was already checked. In both cases, we
should ensure that the view is reached and refreshed during either the
current change detection or the next one (if change detection is not
already running).

We can achieve this by creating all views with the `Dirty` flag set.

fixes angular#52928
  • Loading branch information
atscott committed Dec 6, 2023
1 parent 66124bd commit dc8d60e
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 17 deletions.
12 changes: 6 additions & 6 deletions packages/core/src/render3/component_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,12 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation, rootViewInjector) :
createElementNode(hostRenderer, elementName, getNamespace(elementName));

// Signal components use the granular "RefreshView" for change detection
const signalFlags = (LViewFlags.SignalView | LViewFlags.IsRoot);
// Non-signal components use the traditional "CheckAlways or OnPush/Dirty" change detection
const nonSignalFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
const rootFlags = this.componentDef.signals ? signalFlags : nonSignalFlags;
let rootFlags = LViewFlags.IsRoot;
if (this.componentDef.signals) {
rootFlags |= LViewFlags.SignalView;
} else if (!this.componentDef.onPush) {
rootFlags |= LViewFlags.CheckAlways;
}

let hydrationInfo: DehydratedView|null = null;
if (hostRNode !== null) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/render3/instructions/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export function createLView<T>(
hydrationInfo: DehydratedView|null): LView<T> {
const lView = tView.blueprint.slice() as LView;
lView[HOST] = host;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass |
LViewFlags.Dirty;
if (embeddedViewInjector !== null ||
(parentLView && (parentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector))) {
lView[FLAGS] |= LViewFlags.HasEmbeddedViewInjector;
Expand Down
16 changes: 6 additions & 10 deletions packages/core/test/linker/change_detection_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1135,16 +1135,12 @@ describe(`ChangeDetection`, () => {
expect(() => ctx.checkNoChanges()).toThrowError(errMsgRegExp);
}));

it('should warn when the view has been created in a cd hook', fakeAsync(() => {
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
ctx.componentInstance.a = 1;
expect(() => ctx.detectChanges())
.toThrowError(
/It seems like the view has been created after its parent and its children have been dirty checked/);

// subsequent change detection should run without issues
ctx.detectChanges();
}));
it('should allow view to be created in a cd hook', () => {
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
ctx.componentInstance.a = 1;
ctx.detectChanges();
expect(ctx.nativeElement.innerText).toEqual('1');
});

it('should not throw when two arrays are structurally the same', fakeAsync(() => {
const ctx = _bindSimpleValue('a', TestData);
Expand Down

0 comments on commit dc8d60e

Please sign in to comment.