framework icon indicating copy to clipboard operation
framework copied to clipboard

Is there a way to destroy aurelia manually? force it to trigger detached() in all components

Open jinwangchina opened this issue 9 years ago • 13 comments

I manually deleted the root tag "

" by manipulating DOM directly, but found no detached() called in my custom-element.

I wonder if there is a way to destroy aurelia manually? for example, a api like aurelia.destroy() which will trigger detached() to be called in all components.

jinwangchina avatar Feb 12 '17 21:02 jinwangchina

There isn't a standard way to do this. We need to add one and we'd certainly welcome your contribution. As a work around, there's a semi-private property named root that is the root view. You can call detached and then unbind on that to clean things up.

EisenbergEffect avatar Feb 14 '17 04:02 EisenbergEffect

Thanks EisenbergEffect, it works.

jinwangchina avatar Feb 14 '17 22:02 jinwangchina

Also check this link about this subject.

sebastien-roch avatar May 17 '17 10:05 sebastien-roch

Can we make root public? If there was a method .reset() or .destroy() Would it be enough to call .detached() and .unbind() on root? From what I learned playing with .enhance(), Aurelia stores the Host after the first call to .enhance(). There is also a boolean hostInitialized or something. So if I'm enhancing an iframe's document, the consecutive enhance will not work, so I have to clear the host and the safeguard. At this point I'm concerned about memory leaks. Is it enough of a clean up? Or there are some other structures we need to tear down?

I'm thinking of a .reset() method that resets Aurelia up until after all the configuration was done. And a .destroy() method which should remove everything.

Alexander-Taran avatar Feb 20 '18 20:02 Alexander-Taran

I'm thinking about something like aurelia-testing for ehnancing brown field apps. just .enhance is not enough. Got to be able to enhance iframe on load, tear down on iframe navigate. And utilize mutation observer for inseparability with legacy dom manipulations

Alexander-Taran avatar Feb 21 '18 16:02 Alexander-Taran

@EisenbergEffect

Hello. This would be a great addition and I guess it shouldn't be hard to implement for some core contributor.

The use case is a micro-frontend application that should be created and destroyed multiple times according to conditions (URL match or any DOM event, etc.).

I have tried what you proposed (calling detached & unbind), it invoked lifecycles hooks but didn't remove DOM nodes, the code looks like this:

aurelia.root.view.removeNodes();
aurelia.root.detached();
aurelia.root.unbind();

Calling removeNodes() did the trick and it removes DOM nodes. But other resources are not disposed, this can be seen when making heap snapshot.

I don't know what is the correct approach of doing this since there is no information regarding that. Can this feature be implemented? Or can any code be shared of how to do this?

Thanks in advance.

arturovt avatar May 26 '20 19:05 arturovt

@arturovt can you help point out what's not disposed from your snapshot?

bigopon avatar May 26 '20 21:05 bigopon

@bigopon

I've got such simple code. This is index.ejs:

<body>
  <app></app>
  <button class="mount">Mount</button>
  <button class="unmount">Unmount</button>
</body>

And this is main.ts:

import { bootstrap } from 'aurelia-bootstrapper';

let ref: Aurelia;

function mount() {
  bootstrap(async (aurelia: Aurelia) => {
    aurelia.use.standardConfiguration().developmentLogging();

    ref = await (await aurelia.start()).setRoot(
      PLATFORM.moduleName('app'),
      document.querySelector('app')
    );
  });
}

function unmount() {
  const root = ref['root'];
  root.view.removeNodes();
  root.detached();
  root.unbind();
  ref = null;
}

document.querySelector('.mount').addEventListener('click', mount);
document.querySelector('.unmount').addEventListener('click', unmount);

I actually could create a minimal reproducible example and push it to some GitHub repo, but I'm sure you understand what's going on here w/o a doubt.

It works. I mean if I click mount button then it will render an application and if I click unmount it will call lifecycle hooks and unrender app.

The problem is that if I click mount multiple times then it seems like that Aurelia keeps configuring plugins but doesn't dispose previous ones. For instance, ConsoleAppender keeps logging multiple times: image

And if I open a Chrome snapshot then there are 4 ConsoleAppender instances and they keep creating but don't get GCed.

Any ideas of how to do this in the right way?

arturovt avatar May 28 '20 07:05 arturovt

Thanks for the detailed explanation. Currently we lack a teardown API in v1. I'd imagine such api works by providing a hook during configuration so that plugins can register their corresponding dispose functions:

import { FrameworkConfiguration } from 'aurelia-framework';

export function configure(config: FrameworkConfiguration) {
  config.use....
  config.disposeTask(() => {
    // do dispose work here
  });
}

and new API on Aurelia:

aurelia.stop();

Or, we can do it much simpler way, as the only thing that mis behave at the moment is the console appender, since it's quite a static API. Which means:

let hasAppender = false;
...
...
aurelia.use.standardConfiguration();
if (!hasAppender) {
  aurelia.use.developmentLogging();
}

bigopon avatar May 28 '20 11:05 bigopon

cc @fkleuver @EisenbergEffect We have this in v2 already, though pinging you just in case & awareness

bigopon avatar May 28 '20 11:05 bigopon

@bigopon thank you very much, I will try it today after the job and will give feedback.

Btw aurelia.stop() seems like to be undocumented, I just googled it and searched through Aurelia API docs. That would be very helpful to expose it as a public API and add it to docs.

arturovt avatar May 28 '20 11:05 arturovt

it's a new API, to be added if we decide to go with a new API. The API name comes from v2. In v2, it's accounted for this very scenario, where you could start/stop multiple times repeatedly, confidentially, thanks to @fkleuver

bigopon avatar May 28 '20 11:05 bigopon

@arturovt Thanks for your comment! It helped me with creating a teardown/unmount feature for this package.

I ended up using the Container class to fetch the Aurelia instance outside of the configure() method while maintaining a clean scope.

import { Aurelia } from 'aurelia-framework';
import { Container } from 'aurelia-dependency-injection';

const aurelia = Container.instance.get(Aurelia)

peterpolman avatar Feb 27 '21 16:02 peterpolman