Skip to content

Commit

Permalink
Merge pull request #382 from Sayan751/issue-381
Browse files Browse the repository at this point in the history
feat(compose): bindable activation-strategy
  • Loading branch information
EisenbergEffect authored Jul 12, 2019
2 parents 750f519 + ead0b5c commit 242a259
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 21 deletions.
43 changes: 31 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 42 additions & 6 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@ import { DOM } from 'aurelia-pal';
import { TaskQueue } from 'aurelia-task-queue';
import { bindable, CompositionContext, CompositionEngine, customElement, noView, View, ViewResources, ViewSlot } from 'aurelia-templating';


/**
* Available activation strategies for the view and view-model bound to `<compose/>` element
*
* @export
* @enum {string}
*/
export enum ActivationStrategy {
/**
* Default activation strategy; the 'activate' lifecycle hook will be invoked when the model changes.
*/
InvokeLifecycle = 'invoke-lifecycle',
/**
* The view/view-model will be recreated, when the "model" changes.
*/
Replace = 'replace'
}

/**
* Used to compose a new view / view-model template or bind to an existing instance.
Expand Down Expand Up @@ -39,6 +54,15 @@ export class Compose {
*/
@bindable viewModel: any;

/**
* Strategy to activate the view-model. Default is "invoke-lifecycle".
* Bind "replace" to recreate the view/view-model when the model changes.
*
* @property activationStrategy
* @type {ActivationStrategy}
*/
@bindable activationStrategy: ActivationStrategy = ActivationStrategy.InvokeLifecycle;

/**
* SwapOrder to control the swapping order of the custom element's view.
*
Expand Down Expand Up @@ -226,11 +250,7 @@ function processChanges(composer: Compose) {
const changes = composer.changes;
composer.changes = Object.create(null);

if (!('view' in changes) && !('viewModel' in changes) && ('model' in changes)) {
// just try to activate the current view model
composer.pendingTask = tryActivateViewModel(composer.currentViewModel, changes.model);
if (!composer.pendingTask) { return; }
} else {
if (needsReInitialization(composer, changes)) {
// init context
let instruction = {
view: composer.view,
Expand All @@ -248,6 +268,10 @@ function processChanges(composer: Compose) {
composer.currentController = controller;
composer.currentViewModel = controller ? controller.viewModel : null;
});
} else {
// just try to activate the current view model
composer.pendingTask = tryActivateViewModel(composer.currentViewModel, changes.model);
if (!composer.pendingTask) { return; }
}

composer.pendingTask = composer.pendingTask
Expand All @@ -274,3 +298,15 @@ function requestUpdate(composer: Compose) {
processChanges(composer);
});
}

function needsReInitialization(composer: Compose, changes: any) {
let activationStrategy = composer.activationStrategy;
const vm = composer.currentViewModel;
if (vm && typeof vm.determineActivationStrategy === 'function') {
activationStrategy = vm.determineActivationStrategy();
}

return 'view' in changes
|| 'viewModel' in changes
|| activationStrategy === ActivationStrategy.Replace;
}
100 changes: 98 additions & 2 deletions test/compose.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import './setup';
import { StageComponent, ComponentTester } from 'aurelia-testing';
import { bootstrap } from 'aurelia-bootstrapper';
import { Compose } from '../src/compose';
import { InlineViewStrategy, useView } from 'aurelia-framework';
import { Compose, ActivationStrategy } from '../src/compose';
import { InlineViewStrategy, useView, TaskQueue } from 'aurelia-framework';

describe('compose.integration.spec.ts', () => {

Expand Down Expand Up @@ -81,6 +81,102 @@ describe('compose.integration.spec.ts', () => {
component.dispose();
});

it('works with determineActivationStrategy() - replace', async () => {
const { component, compose } = await bootstrapCompose(
`<compose view-model.bind="viewModel"></compose>`,
{
viewModel: class {
// w/o the get view strategy, the initial composition fails, which results to undefined currentViewModel
getViewStrategy() {
return new InlineViewStrategy('<template></template>');
}

determineActivationStrategy() {
return ActivationStrategy.Replace;
}
}
}
);

const taskQueue = new TaskQueue();
spyOn(compose.compositionEngine, 'compose').and.callThrough();
const oldModel = compose.model;
compose.modelChanged({ a: 1 }, oldModel);

taskQueue.queueMicroTask(() => {
expect(compose.compositionEngine.compose).toHaveBeenCalledTimes(1);
component.dispose();
});

});

it('works with determineActivationStrategy() - invoke-lifecycle', async () => {
let activationCount = 0;
const { component, compose } = await bootstrapCompose(
`<compose view-model.bind="viewModel"></compose>`,
{
viewModel: class {
activate() {
activationCount++;
}

// w/o the get view strategy, the initial composition fails, which results to undefined currentViewModel
getViewStrategy() {
return new InlineViewStrategy('<template></template>');
}

determineActivationStrategy() {
return ActivationStrategy.InvokeLifecycle;
}
}
}
);

const taskQueue = new TaskQueue();

const oldModel = compose.model;
compose.modelChanged({}, oldModel);

taskQueue.queueMicroTask(() => {
expect(activationCount).toBe(2, 'activation count === 2');
component.dispose();
});
});

[1, true, 'chaos', new Date(1900, 1, 1), {}].map((strategy) =>
it(`applies invoke-lifecycle strategy when determineActivationStrategy() returns unknown value such as ${strategy}`, async () => {
let activationCount = 0;
const { component, compose } = await bootstrapCompose(
`<compose view-model.bind="viewModel"></compose>`,
{
viewModel: class {
activate() {
activationCount++;
}

// w/o the get view strategy, the initial composition fails, which results to undefined currentViewModel
getViewStrategy() {
return new InlineViewStrategy('<template></template>');
}

determineActivationStrategy() {
return strategy;
}
}
}
);

const taskQueue = new TaskQueue();

const oldModel = compose.model;
compose.modelChanged({}, oldModel);

taskQueue.queueMicroTask(() => {
expect(activationCount).toBe(2, 'activation count === 2');
component.dispose();
});
}));

describe('scope traversing', () => {
it('traverses scope by default', async () => {
const { component } = await bootstrapCompose(
Expand Down
Loading

0 comments on commit 242a259

Please sign in to comment.