aurelia / framework

The Aurelia 1 framework entry point, bringing together all the required sub-modules of Aurelia.
MIT License
11.76k stars 627 forks source link

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

Open jinwangchina opened 7 years ago

jinwangchina commented 7 years ago

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.

EisenbergEffect commented 7 years ago

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.

jinwangchina commented 7 years ago

Thanks EisenbergEffect, it works.

sebastien-roch commented 7 years ago

Also check this link about this subject.

Alexander-Taran commented 6 years ago

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 commented 6 years ago

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

arturovt commented 4 years ago

@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.

bigopon commented 4 years ago

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

arturovt commented 4 years ago

@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?

bigopon commented 4 years ago

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 commented 4 years ago

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

arturovt commented 4 years ago

@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.

bigopon commented 4 years ago

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

peterpolman commented 3 years ago

@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)