Skip to content

Commit

Permalink
refactor(core): add on immediate support for defer blocks
Browse files Browse the repository at this point in the history
This commit adds a logic to handle `on immediate` conditions both as a main condition, as well as a prefetching condition (i.e. `prefetch on immediate`).
  • Loading branch information
AndrewKushnir committed Sep 7, 2023
1 parent eb0137d commit aab9bc6
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 6 deletions.
33 changes: 27 additions & 6 deletions packages/core/src/render3/instructions/defer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export function ɵɵdeferPrefetchWhen(rawValue: unknown) {
}

/**
* Sets up handlers that represent `on idle` deferred trigger.
* Sets up logic to handle the `on idle` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferOnIdle() {
Expand All @@ -169,7 +169,7 @@ export function ɵɵdeferOnIdle() {
}

/**
* Creates runtime data structures for the `prefetch on idle` deferred trigger.
* Sets up logic to handle the `prefetch on idle` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnIdle() {
Expand All @@ -191,17 +191,38 @@ export function ɵɵdeferPrefetchOnIdle() {
}

/**
* Creates runtime data structures for the `on immediate` deferred trigger.
* Sets up logic to handle the `on immediate` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferOnImmediate() {} // TODO: implement runtime logic.
export function ɵɵdeferOnImmediate() {
const lView = getLView();
const tNode = getCurrentTNode()!;
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);

// If loading template is present - skip rendering of a placeholder
// block, since it would be immediately replaced by the loading block.
if (tDetails.loadingTmplIndex !== null) {
renderPlaceholder(lView, tNode);
}
triggerDeferBlock(lView, tNode);
}


/**
* Creates runtime data structures for the `prefetch on immediate` deferred trigger.
* Sets up logic to handle the `prefetch on immediate` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnImmediate() {} // TODO: implement runtime logic.
export function ɵɵdeferPrefetchOnImmediate() {
const lView = getLView();
const tNode = getCurrentTNode()!;
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);

if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
triggerResourceLoading(tDetails, tView, lView);
}
}

/**
* Creates runtime data structures for the `on timer` deferred trigger.
Expand Down
150 changes: 150 additions & 0 deletions packages/core/test/acceptance/defer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,79 @@ describe('#defer', () => {
expect(fixture.nativeElement.outerHTML).toContain('<my-lazy-cmp>Hi!</my-lazy-cmp>');
});

describe('`on` conditions', () => {
it('should support `on immediate` condition', async () => {
@Component({
selector: 'nested-cmp',
standalone: true,
template: 'Rendering {{ block }} block.',
})
class NestedCmp {
@Input() block!: string;
}

@Component({
standalone: true,
selector: 'root-app',
imports: [NestedCmp],
template: `
{#defer on immediate}
<nested-cmp [block]="'primary'" />
{:placeholder}
Placeholder
{:loading}
Loading
{/defer}
`
})
class RootCmp {
}

let loadingFnInvokedTimes = 0;
const deferDepsInterceptor = {
intercept() {
return () => {
loadingFnInvokedTimes++;
return [Promise.resolve(NestedCmp)];
};
}
};

TestBed.configureTestingModule({
providers: [
{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor},
]
});

clearDirectiveDefs(RootCmp);

const fixture = TestBed.createComponent(RootCmp);
fixture.detectChanges();

// Expecting that no placeholder content would be rendered when
// a `{:loading}` block is present.
expect(fixture.nativeElement.outerHTML).toContain('Loading');

// Expecting loading function to be triggered right away.
expect(loadingFnInvokedTimes).toBe(1);

await fixture.whenStable(); // loading dependencies of the defer block
fixture.detectChanges();

// Expect that the loading resources function was not invoked again.
expect(loadingFnInvokedTimes).toBe(1);

// Verify primary block content.
const primaryBlockHTML = fixture.nativeElement.outerHTML;
expect(primaryBlockHTML)
.toContain(
'<nested-cmp ng-reflect-block="primary">Rendering primary block.</nested-cmp>');

// Expect that the loading resources function was not invoked again (counter remains 1).
expect(loadingFnInvokedTimes).toBe(1);
});
});


describe('directive matching', () => {
it('should support directive matching in all blocks', async () => {
Expand Down Expand Up @@ -1000,5 +1073,82 @@ describe('#defer', () => {
expect(loadingFnInvokedTimes).toBe(1);
});
});

it('should support `prefetch on immediate` condition', async () => {
@Component({
selector: 'nested-cmp',
standalone: true,
template: 'Rendering {{ block }} block.',
})
class NestedCmp {
@Input() block!: string;
}

@Component({
standalone: true,
selector: 'root-app',
imports: [NestedCmp],
template: `
{#defer when deferCond; prefetch on immediate}
<nested-cmp [block]="'primary'" />
{:placeholder}
Placeholder
{/defer}
`
})
class RootCmp {
deferCond = false;
}

let loadingFnInvokedTimes = 0;
const deferDepsInterceptor = {
intercept() {
return () => {
loadingFnInvokedTimes++;
return [Promise.resolve(NestedCmp)];
};
}
};

TestBed.configureTestingModule({
providers: [
{provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, useValue: deferDepsInterceptor},
]
});

clearDirectiveDefs(RootCmp);

const fixture = TestBed.createComponent(RootCmp);
fixture.detectChanges();

expect(fixture.nativeElement.outerHTML).toContain('Placeholder');

// Expecting loading function to be triggered right away.
expect(loadingFnInvokedTimes).toBe(1);

await fixture.whenStable(); // prefetching dependencies of the defer block
fixture.detectChanges();

// Expect that the loading resources function was invoked once.
expect(loadingFnInvokedTimes).toBe(1);

// Expect that placeholder content is still rendered.
expect(fixture.nativeElement.outerHTML).toContain('Placeholder');

// Trigger main content.
fixture.componentInstance.deferCond = true;
fixture.detectChanges();

await fixture.whenStable();

// Verify primary block content.
const primaryBlockHTML = fixture.nativeElement.outerHTML;
expect(primaryBlockHTML)
.toContain(
'<nested-cmp ng-reflect-block="primary">Rendering primary block.</nested-cmp>');

// Expect that the loading resources function was not invoked again (counter remains 1).
expect(loadingFnInvokedTimes).toBe(1);
});
});
});

0 comments on commit aab9bc6

Please sign in to comment.