Angular2 detach view before onDestroy?
Asked Answered
M

3

6

Is it somehow possible in Angular2 to get notified BEFORE the component is already destroyed!? i.e. When it is ABOUT TO be destroyed.

I have a container component which holds a ViewContainerRef on one of its children which will be used to dynamically load views. If the container component itself gets destroyed (in my case due to a *ngFor directive in the parent component), i want to detach the view which is currently loaded in the ViewContainerRef and attach it on another container again.

Thing is: In the ngOnDestroy() life cycle hook the ViewContainerRef is already cleared, so all views are destroyed, nothing to detach anymore.

Microphyte answered 24/6, 2016 at 9:34 Comment(2)
There is no such lifecycle hook. You could make a custom ngFor. The source code is not complex. In this custom ngFor you could call a method on the added component or emit an event that notifies the component about being destroyed.Cadenza
Nice idea. I will give it a tryMicrophyte
M
2

My solution for now (not really a workaround) is to implement the ngOnChanges() of the parent component which uses the ngFor directive. If the array which is iterated by ngFor has changed and its length is shorter than before i simply detach the views of all containers (@ViewChildren containers: QueryList) and map them to the containers (Map). I then re-insert the mapped views again by iterating the new containers list and loading the ViewRefs from the map, in the containers.changed listener.

@Component({
 selector: 'container-collection',
 directives: [Container],
 template: `<container *ngFor="let i of views"></container>`
})
export class ContainerCollection {
 public views = [];

 @ViewChildren(Container) private containers: QueryList<Container>;

 private viewMap = new Map<Container, ViewRef>();

 public ngOnChanges(changes: {[key: string]: SimpleChange]}): void {
  if(changes['views']) {
   //detach all views and remember which container had which view
   this.viewMap = new Map<Container, ViewRef>();

   this.containers.forEach(container => {
    var view = container.viewContainer.detach();
    if(view)
     this.viewMap.set(container, view);
   });
  }
 }

 public ngAfterViewInit(): void {
  //insert the views again (into the appropriate containers)
  this.containers.changes.subscribe( () => {
   this.containers.forEach(container => {
    var view = this.viewMap.get(container);
    if(view)
     container.viewContainer.insert(view);
   });
  });
 }
}

@Component({
 selector: 'container',
 template: `<div #viewContainer></div>`
})
export class Container {
 @ViewChild('viewContainer') public viewContainer;
}

The code is just a draft and may contain syntax errors. But the idea (which is working for me) should be clear.

It would be great of the Angular2 team to add a LifeCycle hook which gets called BEFORE a component gets actually destroyed. (e.g. ngBeforeDestroy() ) or so.

Microphyte answered 27/6, 2016 at 8:19 Comment(1)
Perhaps having a service injected into the parent that tracks the view refs would work. Then you do everything through the service, and it can be made smart enough so the child views will only be recreated when needed. You could then have a directive that also injects the service and is smart enough to accept a template ref, and only create the embedded view once and cache it in the service.Abelmosk
D
1

Maybe you could use OnChanges hook to get notified when the iterated collection has changed and you'll expect the view to be destroyed due to *ngFor.

Then use @ViewChildren annotation to hold list of all rendered components in the *ngFor. Something like:

<my-component
    #mycomponents
    *ngFor="let c of collection"
</my-component>

You can iterate components identified by #mycomponents, find which one has changed and call some custom method that will detach whatever you need.

Another and maybe easier way could be using just @ViewChildren and QueryList because it has property changes which is an Observable<any> so subscribing to it should notify you about any changes to the collection iterated with *ngFor. However, I'm not sure at which point the event is emited so it might be too late as you described with onDestroy.

Dissonancy answered 24/6, 2016 at 10:10 Comment(2)
Thanks for the answer. But yes, it is already too late when the changes event is dispatchedMicrophyte
But the onChanges idea basically worked after all. I added a full answer to get the entire solution.Microphyte
Q
0

Not sure if there is any other lifecycle hook that would satisfy your condition:

const LIFECYCLE_INTERFACES: Map<any, Type> = MapWrapper.createFromPairs([
  [LifecycleHooks.OnInit, OnInit],
  [LifecycleHooks.OnDestroy, OnDestroy],
  [LifecycleHooks.DoCheck, DoCheck],
  [LifecycleHooks.OnChanges, OnChanges],
  [LifecycleHooks.AfterContentInit, AfterContentInit],
  [LifecycleHooks.AfterContentChecked, AfterContentChecked],
  [LifecycleHooks.AfterViewInit, AfterViewInit],
  [LifecycleHooks.AfterViewChecked, AfterViewChecked],
]);

const LIFECYCLE_PROPS: Map<any, string> = MapWrapper.createFromPairs([
  [LifecycleHooks.OnInit, 'ngOnInit'],
  [LifecycleHooks.OnDestroy, 'ngOnDestroy'],
  [LifecycleHooks.DoCheck, 'ngDoCheck'],
  [LifecycleHooks.OnChanges, 'ngOnChanges'],
  [LifecycleHooks.AfterContentInit, 'ngAfterContentInit'],
  [LifecycleHooks.AfterContentChecked, 'ngAfterContentChecked'],
  [LifecycleHooks.AfterViewInit, 'ngAfterViewInit'],
  [LifecycleHooks.AfterViewChecked, 'ngAfterViewChecked'],
]);
Quimby answered 24/6, 2016 at 9:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.