Skip to content

Commit

Permalink
fix(component): fixes recursive rendering (#3255)
Browse files Browse the repository at this point in the history
Closes #3246
  • Loading branch information
e-davidson authored Dec 6, 2021
1 parent 4b9815c commit d24dde1
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 10 deletions.
58 changes: 58 additions & 0 deletions modules/component/spec/let/let.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ChangeDetectorRef,
Component,
Directive,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
Expand All @@ -13,6 +14,7 @@ import {
waitForAsync,
} from '@angular/core/testing';
import {
BehaviorSubject,
EMPTY,
interval,
NEVER,
Expand Down Expand Up @@ -60,6 +62,29 @@ class LetDirectiveTestCompleteComponent {
value$: Observable<number> = of(42);
}

@Directive({
selector: '[recursiveDirective]',
})
export class RecursiveDirective {
constructor(private subject: BehaviorSubject<number>) {
this.subject.next(1);
}
}

@Component({
template: `
<ng-container recursiveDirective *ngrxLet="subject as value">{{
value
}}</ng-container>
`,
})
class LetDirectiveTestRecursionComponent {
constructor(public subject: BehaviorSubject<number>) {}
get value$() {
return this.subject;
}
}

let fixtureLetDirectiveTestComponent: ComponentFixture<LetDirectiveTestComponent>;
let letDirectiveTestComponent: {
value$: ObservableInput<any> | undefined | null;
Expand Down Expand Up @@ -117,6 +142,29 @@ const setupLetDirectiveTestComponentComplete = (): void => {
componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;
};

const setupLetDirectiveTestRecursionComponent = (): void => {
const subject = new BehaviorSubject(0);
TestBed.configureTestingModule({
declarations: [
LetDirectiveTestRecursionComponent,
RecursiveDirective,
LetDirective,
],
providers: [
{ provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },
TemplateRef,
ViewContainerRef,
{ provide: BehaviorSubject, useValue: subject },
],
});
fixtureLetDirectiveTestComponent = TestBed.createComponent(
LetDirectiveTestRecursionComponent
);
letDirectiveTestComponent =
fixtureLetDirectiveTestComponent.componentInstance;
componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;
};

describe('LetDirective', () => {
describe('when nexting values', () => {
beforeEach(waitForAsync(setupLetDirectiveTestComponent));
Expand Down Expand Up @@ -281,4 +329,14 @@ describe('LetDirective', () => {
expect(componentNativeElement.textContent).toBe('true');
});
});

describe('when rendering recursively', () => {
beforeEach(waitForAsync(setupLetDirectiveTestRecursionComponent));

it('should render 2nd emitted value if the observable emits while the view is being rendered', fakeAsync(() => {
fixtureLetDirectiveTestComponent.detectChanges();
expect(letDirectiveTestComponent).toBeDefined();
expect(componentNativeElement.textContent).toBe('1');
}));
});
});
21 changes: 11 additions & 10 deletions modules/component/src/let/let.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class LetDirective<U> implements OnDestroy {
// eslint-disable-next-line @typescript-eslint/naming-convention
static ngTemplateGuard_ngrxLet: 'binding';

private embeddedView: any;
private isEmbeddedViewCreated = false;
private readonly viewContext: LetViewContext<U | undefined | null> = {
$implicit: undefined,
ngrxLet: undefined,
Expand All @@ -107,7 +107,7 @@ export class LetDirective<U> implements OnDestroy {
private readonly resetContextObserver: NextObserver<void> = {
next: () => {
// if not initialized no need to set undefined
if (this.embeddedView) {
if (this.isEmbeddedViewCreated) {
this.viewContext.$implicit = undefined;
this.viewContext.ngrxLet = undefined;
this.viewContext.$error = false;
Expand All @@ -117,26 +117,26 @@ export class LetDirective<U> implements OnDestroy {
};
private readonly updateViewContextObserver: Observer<U | null | undefined> = {
next: (value: U | null | undefined) => {
this.viewContext.$implicit = value;
this.viewContext.ngrxLet = value;
// to have init lazy
if (!this.embeddedView) {
if (!this.isEmbeddedViewCreated) {
this.createEmbeddedView();
}
this.viewContext.$implicit = value;
this.viewContext.ngrxLet = value;
},
error: (error: Error) => {
this.viewContext.$error = true;
// to have init lazy
if (!this.embeddedView) {
if (!this.isEmbeddedViewCreated) {
this.createEmbeddedView();
}
this.viewContext.$error = true;
},
complete: () => {
this.viewContext.$complete = true;
// to have init lazy
if (!this.embeddedView) {
if (!this.isEmbeddedViewCreated) {
this.createEmbeddedView();
}
this.viewContext.$complete = true;
},
};

Expand Down Expand Up @@ -169,7 +169,8 @@ export class LetDirective<U> implements OnDestroy {
}

createEmbeddedView() {
this.embeddedView = this.viewContainerRef.createEmbeddedView(
this.isEmbeddedViewCreated = true;
this.viewContainerRef.createEmbeddedView(
this.templateRef,
this.viewContext
);
Expand Down

0 comments on commit d24dde1

Please sign in to comment.