Skip to content

Commit d24dde1

Browse files
authored
fix(component): fixes recursive rendering (#3255)
Closes #3246
1 parent 4b9815c commit d24dde1

File tree

2 files changed

+69
-10
lines changed

2 files changed

+69
-10
lines changed

modules/component/spec/let/let.directive.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ChangeDetectorRef,
33
Component,
4+
Directive,
45
TemplateRef,
56
ViewContainerRef,
67
} from '@angular/core';
@@ -13,6 +14,7 @@ import {
1314
waitForAsync,
1415
} from '@angular/core/testing';
1516
import {
17+
BehaviorSubject,
1618
EMPTY,
1719
interval,
1820
NEVER,
@@ -60,6 +62,29 @@ class LetDirectiveTestCompleteComponent {
6062
value$: Observable<number> = of(42);
6163
}
6264

65+
@Directive({
66+
selector: '[recursiveDirective]',
67+
})
68+
export class RecursiveDirective {
69+
constructor(private subject: BehaviorSubject<number>) {
70+
this.subject.next(1);
71+
}
72+
}
73+
74+
@Component({
75+
template: `
76+
<ng-container recursiveDirective *ngrxLet="subject as value">{{
77+
value
78+
}}</ng-container>
79+
`,
80+
})
81+
class LetDirectiveTestRecursionComponent {
82+
constructor(public subject: BehaviorSubject<number>) {}
83+
get value$() {
84+
return this.subject;
85+
}
86+
}
87+
6388
let fixtureLetDirectiveTestComponent: ComponentFixture<LetDirectiveTestComponent>;
6489
let letDirectiveTestComponent: {
6590
value$: ObservableInput<any> | undefined | null;
@@ -117,6 +142,29 @@ const setupLetDirectiveTestComponentComplete = (): void => {
117142
componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;
118143
};
119144

145+
const setupLetDirectiveTestRecursionComponent = (): void => {
146+
const subject = new BehaviorSubject(0);
147+
TestBed.configureTestingModule({
148+
declarations: [
149+
LetDirectiveTestRecursionComponent,
150+
RecursiveDirective,
151+
LetDirective,
152+
],
153+
providers: [
154+
{ provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },
155+
TemplateRef,
156+
ViewContainerRef,
157+
{ provide: BehaviorSubject, useValue: subject },
158+
],
159+
});
160+
fixtureLetDirectiveTestComponent = TestBed.createComponent(
161+
LetDirectiveTestRecursionComponent
162+
);
163+
letDirectiveTestComponent =
164+
fixtureLetDirectiveTestComponent.componentInstance;
165+
componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;
166+
};
167+
120168
describe('LetDirective', () => {
121169
describe('when nexting values', () => {
122170
beforeEach(waitForAsync(setupLetDirectiveTestComponent));
@@ -281,4 +329,14 @@ describe('LetDirective', () => {
281329
expect(componentNativeElement.textContent).toBe('true');
282330
});
283331
});
332+
333+
describe('when rendering recursively', () => {
334+
beforeEach(waitForAsync(setupLetDirectiveTestRecursionComponent));
335+
336+
it('should render 2nd emitted value if the observable emits while the view is being rendered', fakeAsync(() => {
337+
fixtureLetDirectiveTestComponent.detectChanges();
338+
expect(letDirectiveTestComponent).toBeDefined();
339+
expect(componentNativeElement.textContent).toBe('1');
340+
}));
341+
});
284342
});

modules/component/src/let/let.directive.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class LetDirective<U> implements OnDestroy {
9494
// eslint-disable-next-line @typescript-eslint/naming-convention
9595
static ngTemplateGuard_ngrxLet: 'binding';
9696

97-
private embeddedView: any;
97+
private isEmbeddedViewCreated = false;
9898
private readonly viewContext: LetViewContext<U | undefined | null> = {
9999
$implicit: undefined,
100100
ngrxLet: undefined,
@@ -107,7 +107,7 @@ export class LetDirective<U> implements OnDestroy {
107107
private readonly resetContextObserver: NextObserver<void> = {
108108
next: () => {
109109
// if not initialized no need to set undefined
110-
if (this.embeddedView) {
110+
if (this.isEmbeddedViewCreated) {
111111
this.viewContext.$implicit = undefined;
112112
this.viewContext.ngrxLet = undefined;
113113
this.viewContext.$error = false;
@@ -117,26 +117,26 @@ export class LetDirective<U> implements OnDestroy {
117117
};
118118
private readonly updateViewContextObserver: Observer<U | null | undefined> = {
119119
next: (value: U | null | undefined) => {
120+
this.viewContext.$implicit = value;
121+
this.viewContext.ngrxLet = value;
120122
// to have init lazy
121-
if (!this.embeddedView) {
123+
if (!this.isEmbeddedViewCreated) {
122124
this.createEmbeddedView();
123125
}
124-
this.viewContext.$implicit = value;
125-
this.viewContext.ngrxLet = value;
126126
},
127127
error: (error: Error) => {
128+
this.viewContext.$error = true;
128129
// to have init lazy
129-
if (!this.embeddedView) {
130+
if (!this.isEmbeddedViewCreated) {
130131
this.createEmbeddedView();
131132
}
132-
this.viewContext.$error = true;
133133
},
134134
complete: () => {
135+
this.viewContext.$complete = true;
135136
// to have init lazy
136-
if (!this.embeddedView) {
137+
if (!this.isEmbeddedViewCreated) {
137138
this.createEmbeddedView();
138139
}
139-
this.viewContext.$complete = true;
140140
},
141141
};
142142

@@ -169,7 +169,8 @@ export class LetDirective<U> implements OnDestroy {
169169
}
170170

171171
createEmbeddedView() {
172-
this.embeddedView = this.viewContainerRef.createEmbeddedView(
172+
this.isEmbeddedViewCreated = true;
173+
this.viewContainerRef.createEmbeddedView(
173174
this.templateRef,
174175
this.viewContext
175176
);

0 commit comments

Comments
 (0)