Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

destructor option for service providers #11228

Closed
pjordaan opened this issue Sep 1, 2016 · 30 comments · Fixed by #13369
Closed

destructor option for service providers #11228

pjordaan opened this issue Sep 1, 2016 · 30 comments · Fixed by #13369
Labels
area: core Issues related to the framework runtime feature Issue that requests a new feature freq1: low

Comments

@pjordaan
Copy link

pjordaan commented Sep 1, 2016

[ ] bug report => search github for a similar issue or PR before submitting
[X ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
See example below:

@Injectable()
export class GlobalServiceProvidedInBootstrap {
    public subscribe(f: Function);
    public unsubscribe(f: Function);
}
export function callback() {
};

@Injectable()
export class ServiceProvidedInElement {
    constructor(public service: GlobalServiceProvidedInBootstrap) {
        service.subscribe(callback);
    }
}
@Component({
    selector: 'example-tag',
     providers: [ServiceProvidedInElement]
})
export class ExampleTagComponent {
    constructor(private s: ServiceProvidedInElement) {
    }
    public ngOnDestroy() {
        this.s.service.unsubscribe(callback);
    }
}

The problem I'm having in similar code is that the moment this component is destroyed, there is no way for me to unsubscribe ServiceProvidedInElement from GlobalServiceProvidedInBootstrap unless I use ngOnDestroy in the component.

Expected/desired behavior
The expected behavior is that when I declare my providers, I should be able to set a callback to destroy my service provided. So instead of using ngOnDestroy I could do it like this:

export SERVICE_PROVIDED_IN_ELEMENT_PROVIDERS = [
    {
        token: ServiceProvidedInElement,
        useDestructor: function (element: ServiceProvidedInElement) {
              element.service.unsubscribe(callback);
        }
    }
];
@vicb vicb added feature Issue that requests a new feature comp: core labels Sep 1, 2016
@mhevery mhevery added area: core Issues related to the framework runtime and removed comp: core labels Sep 7, 2016
@DzmitryShylovich
Copy link
Contributor

DzmitryShylovich commented Nov 12, 2016

Services already have ngOnDestroy hook so it can be closed.

@zoechi
Copy link
Contributor

zoechi commented Nov 14, 2016

I have never seen it mentioned that ngOnDestroy is supported on services.
The docs say "when a directives or pipe is destroyed" https://angular.io/docs/ts/latest/api/core/index/OnDestroy-class.html

@DzmitryShylovich
Copy link
Contributor

http://plnkr.co/edit/AgBYNxmW1aIPiDFAr5iu?p=preview

@zoechi
Copy link
Contributor

zoechi commented Nov 14, 2016

I guess the docs on the interface should be updated then

DzmitryShylovich pushed a commit to DzmitryShylovich/angular that referenced this issue Dec 10, 2016
vicb pushed a commit that referenced this issue Dec 13, 2016
vicb pushed a commit that referenced this issue Dec 15, 2016
@maxkoretskyi
Copy link

maxkoretskyi commented Jun 7, 2017

@DzmitryShylovich, there is no longer logging in the console in the plunker you referenced when a component is destroyed. Has anything changed?

@willshowell
Copy link
Contributor

@MaximusK there was a change in 4.0.0-rc.4 that made providers instantiate lazily.

Updated plunker: http://plnkr.co/edit/FA9bIc3dEiHundiKx95M?p=preview

@maxkoretskyi
Copy link

@willshowell , thanks! So as I understand the ngOnDestroy is called on a service when it's destroyed. But when is it destroyed? When the component that declared the service in providers gets destroyed? And does it mean that services defined on modules will never be destroyed?

@willshowell
Copy link
Contributor

@MaximusK From what I understand, yes to both questions. For our case, we wished for a service to be destroyed once navigating away from any of the module's routes. This required providing (and injecting) the service in a wrapper <router-outlet> component.

@gparlakov
Copy link
Contributor

gparlakov commented Sep 4, 2017

@willshowell @MaximusK I have the same observations. Service gets its ngOnDestroy() called only when provided in a component. So the Service will never get destroyed if provided in module.

And I ask - does that matter - if you "destroy" the ng app - that means you have closed that page so effectively the garbage collector will clean up all eventually.

@maxkoretskyi
Copy link

maxkoretskyi commented Sep 4, 2017

@willshowell , thanks. Interesting approach with a router-outlet wrapper. Can you maybe share more details?

@gparlakov , thanks for the confirmation

@willshowell
Copy link
Contributor

willshowell commented Sep 5, 2017

@MaximusK it works like this:

@Component({
  ...
  providers: [MyService],
  template: '<router-outlet></router-outlet>'
})
export class MyNavWrapperComponent {
  // Must inject to instantiate service for all child routes.
  // When this component is destroyed, the service will be too.
  constructor(service: MyService) { }
}
routes: Routes = [
  {
    path: '',
    component: MyNavWrapperComponent,
    children: [ ... ],
  }
]

It's important to note that using empty routes like this may introduce confusing behavior with relative routes since that empty route is still considered a UrlSegment. See #17957.

@maxkoretskyi
Copy link

@willshowell , thanks for sharing

@mcgraphix
Copy link

@gparlakov - I don't think we should assume that destroying an app only happens when you close the window or navigate away. Let's say your app is loaded into a portal with potentially other apps. IMHO - removing the dom node that the app is bootstrapped on should destroy everything including services provided via modules.

@aks1994
Copy link

aks1994 commented Apr 14, 2018

Is there any manual way to destroy the services provided in a module?

@gparlakov
Copy link
Contributor

gparlakov commented Apr 15, 2018 via email

@aks1994
Copy link

aks1994 commented Apr 15, 2018

@gparlakov My use case is that I have a service that I want to be associated with a component (or only inside a lazy module that opens post logging into my app). However, if I provide it in the component (the main wrapper component post login), I cannot access it in route resolver guards. So I have to provide it at the lazy module level but then it becomes a global service to some extent. Even upon logout from my app (routing to a component outside the lazy module), the service is not destroyed so I have user data in that service which is still in memory. Sure, I can't access it directly from outside the lazy module but it is still not deleted permanently and is reaccessed upon logging in (even with a different log in)

@gparlakov
Copy link
Contributor

@aks1994 This is my understanding of what happens:

  1. Whenever the lazy module is first loaded the service gets instantiated and placed in the module's injector
  2. Every subsequent request to that module (to its main component) would use that same instance

Right?
(I am not aware of a way of instructing angular to kill off any module or its providers.)

Provided that you already have guards: Maybe inside of a route guard that is executed on the way in you can clean up/initialize the service i.e. every time user wants to go back to this component the service-s state gets zeroed out.
Alternatively the service could become a provider of a service-s instead of doing the work itself.

@aks1994
Copy link

aks1994 commented Apr 15, 2018 via email

@gparlakov
Copy link
Contributor

gparlakov commented Apr 15, 2018

@aks1994

For the clean up - I would do it just like you said - set everything back to initial value. And do it /initialize it/ every time you want to use it. The risk is that there might be a confusion about whether or not the state was initialized - so I'd do it every time.

Elaborating on the provider of services: here. Notice the 'the-service.service.ts' and how it is provided in the module and used in hello.component.ts and app.component.ts. It is providing a new instance for every time we call the .provideAService('somestring') method.

@aks1994
Copy link

aks1994 commented Apr 16, 2018

@gparlakov

Okay, makes sense. Thanks for the help and sharing the info on provider of services!

@jscharett
Copy link

This seems like a bit of a hack, imo. It would be nice to simply specify my provider in the module and allow the module to destroy it when the user exits the module. The current design can be left in place for backwards compatibility, but why not allow me to specify that the provider gets destroyed every time the use exits the module?

@mlc-mlapis
Copy link
Contributor

@jscharett ... what is the term exit from the module? What it means for you.

@jscharett
Copy link

@mlc-mlapis When my route changes to leave a lazy loaded module. I understand that lazy loading only delays the loading of the module and does not "unload" it when navigating away from it. It just seems like there should be a clean way to "reset/destroy" a provider used in a lazy loaded module.

For example, in my app, we have a lazy loaded module that when the user enters, create a provider for storing various data. The provider is used across multiple components, so its created in the ng module. When the user navigates away from the module back to the home page, I am required to implement a guard so that I can "cleanup" the data stored in the provider. Not a huge issue, but just seems like it would be beneficial if you could specify a provider that could be destroyed upon exiting the module/route.

It was not intuitive to me that my provider would continue after navigating away from the module.

@Sahil624
Copy link

Hi !
I am also facing same issue. My provider stores some user data that is shared among one or more components for a logged in user. When user logs out and login with any other account user data of previous user is shown in every component.
Any solution or workaround available ?
Thanks for any help.

@shikharseth
Copy link

@Sahil624 any solution you found out for your use case?
It would be great if you can help me in that too

@gparlakov
Copy link
Contributor

@Sahil624 @shikharseth One approach would be providing the provider in the component that needs it. Then It will get instantiated and destroyed along with the component. I am assuming this component would get destroyed when user logs out - because app navigates to logout screen for example

@shikharseth
Copy link

@gparlakov the use case is quite different here. Since, I require service data to be shared across components so I need to mention my providers inside my app.module.ts. Your's approach wont let me do the same.
Data sharing is working fine as to my need across different module and component.
But the scenario is when I logout from one user and login with different app.module don't get destroyed(though component do), due to this service data remains intact and I get same data of my last user on sigining with different user.
I want to destroy data from my services data defined in app.module

@gparlakov
Copy link
Contributor

gparlakov commented May 22, 2019

I want to destroy data from my services data defined in app.module

@shikharseth It appears that you need to use the onDestroy lifecycle hook to clean up your service.
Or the logout method needs to actually clear the state of that service. Either way I would create a method on that service onLogout and call it.

@Sahil624
Copy link

Sahil624 commented May 22, 2019

@Sahil624 any solution you found out for your use case?
It would be great if you can help me in that too

Hi !
Sorry didn't knew about your reply earlier.I did this by making an event service. It was basically a subject in service and I subscribed it in my services which I wanted to destroy (More like i wanted to clear data that was cached in that service). In my logout function i emitted a event (subjectName.next()). Whenever is received anything in by event subscription I cleared my data.
This is more of a hack. But it was working for me. I cannot share my code at this time but i hope I could explain it will. Feel free in any case of doubt.
Thanks

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime feature Issue that requests a new feature freq1: low
Projects
None yet
Development

Successfully merging a pull request may close this issue.