Recompute route-idle helper on transition change
MODIFY
addon/helpers/route-idle.js
addon/helpers/route-idle.js
import Helper from '@ember/component/helper';
-import { tracked } from '@glimmer/tracking';
-import { whenRouteIdle } from '../scheduler';
+import scheduler from '../scheduler';
export default class RouteIdle extends Helper {
- @tracked isIdle = false;
-
compute() {
- if (this.isIdle) {
- return true;
- }
-
- whenRouteIdle().then(() => {
- this.isIdle = true;
- });
-
- return false;
+ return scheduler.isIdle;
}
}

MODIFY
addon/scheduler.ts
addon/scheduler.ts
-import { Promise } from 'rsvp';
-import { schedule } from '@ember/runloop';
import { deprecate } from '@ember/debug';
import { registerDestructor } from '@ember/destroyable';
+import { addListener } from '@ember/object/events';
import Router from '@ember/routing/router';
import type RouterService from '@ember/routing/router-service';
+import { schedule } from '@ember/runloop';
import Service from '@ember/service';
-import { addListener } from '@ember/object/events';
-import { gte } from 'ember-compatibility-helpers';
import { buildWaiter, Token } from '@ember/test-waiters';
+import { tracked } from '@glimmer/tracking';
+import { gte } from 'ember-compatibility-helpers';
+import { Promise } from 'rsvp';
interface Deferred {
isResolved: boolean;
@@ -22,12 +23,17 @@ const APP_SCHEDULER_HAS_SETUP: string = '__APP_SCHEDULER_HAS_SETUP__';
let _whenRouteDidChange: Deferred;
let _whenRouteIdle: Promise<unknown>;
-export const SIMPLE_CALLBACK = (callback: Function) => callback();
const IS_FASTBOOT = typeof (<any>window).FastBoot !== 'undefined';
const waiter = buildWaiter('ember-app-scheduler-waiter');
reset();
+class Scheduler {
+ @tracked isIdle = false;
+}
+const scheduler = new Scheduler();
+export default scheduler;
+
/**
* Initializes the top level promise that initiates the post-render work.
*/
@@ -49,6 +55,7 @@ export function beginTransition(): void {
measure('appScheduler', 'appSchedulerStart', 'appSchedulerEnd');
});
});
+ scheduler.isIdle = false;
}
}
@@ -57,6 +64,7 @@ export function beginTransition(): void {
*/
export function endTransition(): void {
_whenRouteDidChange.resolve();
+ scheduler.isIdle = true;
mark('appSchedulerStart');
}

ADD
tests/dummy/app/controllers/route-idle-helper.js
+import Controller from '@ember/controller';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+
+export default class RouteIdleHelperController extends Controller {
+ queryParams = ['refresh'];
+
+ @tracked refresh = 0;
+
+ @action
+ updateRefreshQP() {
+ this.refresh += 1;
+ }
+}

MODIFY
tests/dummy/app/router.js
tests/dummy/app/router.js
@@ -20,6 +20,7 @@ Router.map(function() {
this.route('content-paint');
this.route('aborted-paint');
this.route('both-painted');
+ this.route('route-idle-helper');
this.route('not-found', { path: '/*path' });
});

ADD
tests/dummy/app/routes/route-idle-helper.js
+import Route from '@ember/routing/route';
+import { Promise } from 'rsvp';
+
+export default class RouteIdleHelperRoute extends Route {
+ queryParams = {
+ refresh: {
+ refreshModel: true,
+ },
+ };
+
+ model() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, 1000);
+ });
+ }
+}

MODIFY
tests/dummy/app/styles/app.css
tests/dummy/app/styles/app.css
+.only-when-route-not-idle {
+ color: green;
+}

MODIFY
tests/dummy/app/templates/docs/route-idle-helper.md
tests/dummy/app/templates/docs/route-idle-helper.md
The `route-idle` helper allows you to use `ember-app-scheduler`'s `whenRouteIdle` functionality directly in your template. Using this helper will allow you to add hidden content that is to be deferred until the route is considered idle.
+Note that `route-idle` will recompute when a router transition is triggered (e.g. updating the model on a route).
+
```hbs
{{#if (route-idle)}}
<span id="content-to-defer">Content to only show once the route is idle</span>
+{{else}}
+ <span id="content-to-show-while-working">Content to only show while the route is not idle</span>
{{/if}}
```

ADD
tests/dummy/app/templates/route-idle-helper.hbs
+<div>
+ {{#if (route-idle)}}
+ <span class="only-when-route-idle">When Route Idle</span>
+ {{else}}
+ <span class="only-when-route-not-idle">When Route Not Idle</span>
+ {{/if}}
+
+ <button class="refresh-button" type="button" {{on "click" this.updateRefreshQP}}>Refresh</button>
+</div>
\ No newline at end of file

MODIFY
tests/integration/helpers/route-idle-test.js
tests/integration/helpers/route-idle-test.js
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
import { render, settled } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
import { beginTransition, endTransition } from 'ember-app-scheduler';
+import { setupRenderingTest } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import { module, test } from 'qunit';
module('Integration | Helper | route-idle', function (hooks) {
setupRenderingTest(hooks);
test('deferred content is visible following whenRouteIdle', async function (assert) {
+ assert.expect(2);
+
beginTransition();
await render(hbs`
@@ -26,4 +28,57 @@ module('Integration | Helper | route-idle', function(hooks) {
await settled();
});
+
+ test('deferred content is hidden following subsequent transitions', async function (assert) {
+ assert.expect(4);
+
+ // FIRST TRANSITION: START
+ beginTransition();
+
+ await render(hbs`
+ {{#if (route-idle)}}
+ <span id="i-exist">Idle content</span>
+ {{/if}}
+ `);
+
+ assert
+ .dom('#i-exist')
+ .doesNotExist(
+ 'deferred content is hidden while initial transition begins'
+ );
+
+ endTransition();
+
+ await settled();
+
+ assert
+ .dom('#i-exist')
+ .exists('deferred content is rendered after initial transition finishes');
+
+ await settled();
+ // FIRST TRANSITION: END
+
+ // SIMULATE SECOND TRANSITION: START
+ this.owner.lookup('service:router').trigger('routeWillChange');
+ beginTransition();
+
+ await settled();
+
+ assert
+ .dom('#i-exist')
+ .doesNotExist(
+ 'deferred content is hidden while second transition is entered'
+ );
+
+ endTransition();
+
+ await settled();
+
+ assert
+ .dom('#i-exist')
+ .exists('deferred content is rendered after second transition finishes');
+
+ await settled();
+ // SIMULATE SECOND TRANSITION: END
+ });
});

MODIFY
tsconfig.json
tsconfig.json
{
"compilerOptions": {
+ "experimentalDecorators": true,
"target": "es2017",
"allowJs": true,
"moduleResolution": "node",

Recompute route-idle helper on transition change (#899)