diff --git a/packages/core/src/defer/dom_triggers.ts b/packages/core/src/defer/dom_triggers.ts index 4f75bc2bc0f0..d30eaad28fd1 100644 --- a/packages/core/src/defer/dom_triggers.ts +++ b/packages/core/src/defer/dom_triggers.ts @@ -31,6 +31,7 @@ import { TriggerType, } from './interfaces'; import {getLDeferBlockDetails} from './utils'; +import {shouldTriggerDeferBlock} from './rendering'; /** * Wrapper for onViewport trigger with angular specific Injector for resolving NgZone instance @@ -134,6 +135,10 @@ export function registerDomTrigger( type: TriggerType, options?: O, ) { + if (!shouldTriggerDeferBlock(type, initialLView)) { + return; + } + const injector = initialLView[INJECTOR]; const zone = injector.get(NgZone); let poll: AfterRenderRef; diff --git a/packages/core/src/defer/rendering.ts b/packages/core/src/defer/rendering.ts index 9fe262dada72..e5fab5dcf57e 100644 --- a/packages/core/src/defer/rendering.ts +++ b/packages/core/src/defer/rendering.ts @@ -7,7 +7,6 @@ */ import {CachedInjectorService} from '../cached_injector_service'; -import {NotificationSource} from '../change_detection/scheduling/zoneless_scheduling'; import {EnvironmentInjector, InjectionToken, Injector, Provider} from '../di'; import { DehydratedContainerView, @@ -26,6 +25,7 @@ import {assertDefined} from '../util/assert'; import { DEFER_BLOCK_STATE, + DeferBlockBehavior, DeferBlockConfig, DeferBlockDependencyInterceptor, DeferBlockInternalState, @@ -40,6 +40,7 @@ import { SSR_BLOCK_STATE, STATE_IS_FROZEN_UNTIL, TDeferBlockDetails, + TriggerType, } from './interfaces'; import {scheduleTimerTrigger} from './timer_scheduler'; import { @@ -485,3 +486,21 @@ export function ɵɵdeferEnableTimerScheduling( applyDeferBlockStateWithSchedulingImpl = applyDeferBlockStateWithScheduling; } } + +/** + * Determines whether we should proceed with triggering a given defer block. + */ +export function shouldTriggerDeferBlock(triggerType: TriggerType, lView: LView): boolean { + // prevents triggering regular triggers when on the server. + if (triggerType === TriggerType.Regular && typeof ngServerMode !== 'undefined' && ngServerMode) { + return false; + } + + // prevents triggering in the case of a test run with manual defer block configuration. + const injector = lView[INJECTOR]; + const config = injector.get(DEFER_BLOCK_CONFIG, null, {optional: true}); + if (config?.behavior === DeferBlockBehavior.Manual) { + return false; + } + return true; +} diff --git a/packages/core/src/defer/triggering.ts b/packages/core/src/defer/triggering.ts index a1b865dea51b..cd69a12e73e9 100644 --- a/packages/core/src/defer/triggering.ts +++ b/packages/core/src/defer/triggering.ts @@ -41,7 +41,6 @@ import {onViewportWrapper} from './dom_triggers'; import {onIdle} from './idle_scheduler'; import { DEFER_BLOCK_STATE, - DeferBlockBehavior, DeferBlockState, DeferBlockTrigger, DeferDependenciesLoadingState, @@ -56,11 +55,11 @@ import { } from './interfaces'; import {DEHYDRATED_BLOCK_REGISTRY, DehydratedBlockRegistry} from './registry'; import { - DEFER_BLOCK_CONFIG, DEFER_BLOCK_DEPENDENCY_INTERCEPTOR, renderDeferBlockState, renderDeferStateAfterResourceLoading, renderPlaceholder, + shouldTriggerDeferBlock, } from './rendering'; import {onTimer} from './timer_scheduler'; import { @@ -313,24 +312,6 @@ export function triggerResourceLoading( }); } -/** - * Defines whether we should proceed with triggering a given defer block. - */ -function shouldTriggerDeferBlock(triggerType: TriggerType, lView: LView): boolean { - // prevents triggering regular triggers when on the server. - if (triggerType === TriggerType.Regular && typeof ngServerMode !== 'undefined' && ngServerMode) { - return false; - } - - // prevents triggering in the case of a test run with manual defer block configuration. - const injector = lView[INJECTOR]; - const config = injector.get(DEFER_BLOCK_CONFIG, null, {optional: true}); - if (config?.behavior === DeferBlockBehavior.Manual) { - return false; - } - return true; -} - /** * Attempts to trigger loading of defer block dependencies. * If the block is already in a loading, completed or an error state - diff --git a/packages/core/test/acceptance/defer_spec.ts b/packages/core/test/acceptance/defer_spec.ts index 75f452160039..dbce77f13761 100644 --- a/packages/core/test/acceptance/defer_spec.ts +++ b/packages/core/test/acceptance/defer_spec.ts @@ -48,7 +48,15 @@ import {ChainedInjector} from '../../src/render3/chained_injector'; import {getComponentDef} from '../../src/render3/def_getters'; import {getInjectorResolutionPath} from '../../src/render3/util/injector_discovery_utils'; import {global} from '../../src/util/global'; -import {ComponentFixture, DeferBlockBehavior, fakeAsync, flush, TestBed, tick} from '../../testing'; +import { + ComponentFixture, + DeferBlockBehavior, + DeferBlockState, + fakeAsync, + flush, + TestBed, + tick, +} from '../../testing'; /** * Clears all associated directive defs from a given component class. @@ -4532,7 +4540,6 @@ describe('@defer', () => { @placeholder {} } `, - changeDetection: ChangeDetectionStrategy.Eager,}) class MyCmp { items = [1, 2, 3, 4, 5, 6]; @@ -4652,6 +4659,36 @@ describe('@defer', () => { expect(activeObservers[2].observedElements.has(button)).toBe(true); expect(activeObservers[2].options).toEqual({rootMargin: '1vh'}); })); + + it('should not attach observer if rendering manually', async () => { + @Component({ + template: ` + @defer (on viewport(trigger)) { + Main content + } @placeholder { + Placeholder + } + + + `, + }) + class MyCmp {} + + TestBed.configureTestingModule({ + deferBlockBehavior: DeferBlockBehavior.Manual, + }); + const fixture = TestBed.createComponent(MyCmp); + fixture.detectChanges(); + + expect(activeObservers.length).toBe(0); + expect(fixture.nativeElement.textContent.trim()).toBe('Placeholder'); + + const deferBlock = (await fixture.getDeferBlocks())[0]; + await deferBlock.render(DeferBlockState.Complete); + + expect(activeObservers.length).toBe(0); + expect(fixture.nativeElement.textContent.trim()).toBe('Main content'); + }); }); describe('DOM-based events cleanup', () => {