Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(component): clear LetDirective view when replaced observable is in suspense state #3671

Merged
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
12 changes: 5 additions & 7 deletions modules/component/spec/let/let.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ class LetDirectiveTestCompleteComponent {

@Component({
template: `
<ng-container *ngrxLet="value$ as value; suspense as s">{{
s ? 'suspense' : value
}}</ng-container>
<ng-container *ngrxLet="value$ as value">{{ value }}</ng-container>
`,
})
class LetDirectiveTestSuspenseComponent {
Expand Down Expand Up @@ -338,13 +336,13 @@ describe('LetDirective', () => {
expect(componentNativeElement.textContent).toBe('42');
}));

it('should render undefined as value when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => {
it('should clear the view when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => {
letDirectiveTestComponent.value$ = of(42);
fixtureLetDirectiveTestComponent.detectChanges();
expect(componentNativeElement.textContent).toBe('42');
letDirectiveTestComponent.value$ = NEVER;
fixtureLetDirectiveTestComponent.detectChanges();
expect(componentNativeElement.textContent).toBe('undefined');
expect(componentNativeElement.textContent).toBe('');
});

it('should render new value when a new observable was passed', () => {
Expand Down Expand Up @@ -453,12 +451,12 @@ describe('LetDirective', () => {
expect(componentNativeElement.textContent).toBe('true');
}));

it('should render suspense when next observable is in suspense state', fakeAsync(() => {
it('should clear the view when next observable is in suspense state', fakeAsync(() => {
letDirectiveTestComponent.value$ = of(true);
fixtureLetDirectiveTestComponent.detectChanges();
letDirectiveTestComponent.value$ = of(false).pipe(delay(1000));
fixtureLetDirectiveTestComponent.detectChanges();
expect(componentNativeElement.textContent).toBe('suspense');
expect(componentNativeElement.textContent).toBe('');
tick(1000);
fixtureLetDirectiveTestComponent.detectChanges();
expect(componentNativeElement.textContent).toBe('false');
Expand Down
11 changes: 1 addition & 10 deletions modules/component/src/let/let.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ export interface LetViewContext<PO> {
* `*ngrxLet="obs$; let c = complete"` or `*ngrxLet="obs$; complete as c"`
*/
complete: boolean;
/**
* `*ngrxLet="obs$; let s = suspense"` or `*ngrxLet="obs$; suspense as s"`
*/
suspense: boolean;
}

/**
Expand Down Expand Up @@ -119,22 +115,19 @@ export class LetDirective<PO> implements OnInit, OnDestroy {
ngrxLet: undefined,
error: undefined,
complete: false,
suspense: true,
};
private readonly renderEventManager = createRenderEventManager<PO>({
suspense: () => {
this.viewContext.$implicit = undefined;
this.viewContext.ngrxLet = undefined;
this.viewContext.error = undefined;
this.viewContext.complete = false;
this.viewContext.suspense = true;

this.renderSuspenseView();
},
next: (event) => {
this.viewContext.$implicit = event.value;
this.viewContext.ngrxLet = event.value;
this.viewContext.suspense = false;

if (event.reset) {
this.viewContext.error = undefined;
Expand All @@ -145,7 +138,6 @@ export class LetDirective<PO> implements OnInit, OnDestroy {
},
error: (event) => {
this.viewContext.error = event.error;
this.viewContext.suspense = false;

if (event.reset) {
this.viewContext.$implicit = undefined;
Expand All @@ -158,7 +150,6 @@ export class LetDirective<PO> implements OnInit, OnDestroy {
},
complete: (event) => {
this.viewContext.complete = true;
this.viewContext.suspense = false;

if (event.reset) {
this.viewContext.$implicit = undefined;
Expand Down Expand Up @@ -224,7 +215,7 @@ export class LetDirective<PO> implements OnInit, OnDestroy {
}

private renderSuspenseView(): void {
if (this.suspenseTemplateRef && this.isMainViewCreated) {
if (this.isMainViewCreated) {
this.isMainViewCreated = false;
this.viewContainerRef.clear();
}
Expand Down
53 changes: 53 additions & 0 deletions projects/ngrx.io/content/guide/migration/v15.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,56 @@ AFTER:
...
</ng-container>
```

#### LetDirective Behavior on Suspense Event

The `LetDirective` view will be cleared when the replaced observable is in a suspense state.
Also, the `suspense` property is removed from the `LetViewContext` because it would always be `false` when the `LetDirective` view is rendered.
Instead of `suspense` property, use [suspense template](guide/component/let#using-suspense-template) to handle the suspense state.

BEFORE:

The `LetDirective` view will not be cleared when the replaced observable is in a suspense state and the suspense template is not passed:

```ts
@Component({
template: `
<!-- When button is clicked, the 'LetDirective' view won't be cleared. -->
<!-- Instead, the value of 'o' will be 'undefined' until the replaced -->
<!-- observable emits the first value (after 1 second). -->
<p *ngrxLet="obs$ as o">{{ o }}</p>
<button (click)="replaceObs()">Replace Observable</button>
`
})
export class TestComponent {
obs$ = of(1);

replaceObs(): void {
this.obs$ = of(2).pipe(delay(1000));
}
}
```

AFTER:

The `LetDirective` view will be cleared when the replaced observable is in a suspense state and the suspense template is not passed:

```ts
@Component({
template: `
<!-- When button is clicked, the 'LetDirective' view will be cleared. -->
<!-- The view will be created again when the replaced observable -->
<!-- emits the first value (after 1 second). -->
<p *ngrxLet="obs$ as o">{{ o }}</p>
<button (click)="replaceObs()">Replace Observable</button>
`
})
export class TestComponent {
obs$ = of(1);

replaceObs(): void {
this.obs$ = of(2).pipe(delay(1000));
}
}
```