2

I would like to know if is possible to do something like the following in Angular 2. Having a component with this template (for example):

ParentComponent template

<div>
  <childOne></childOne>

  <childTwo *ngFor="let item of items">
    whatever
  </childTwo>
</div>

Is there any way to remove these items using the ViewContainerRef from the ParentComponent, like the following?

var viewContainerRef = /* get ParentComponent ViewContainerRef */
var index = viewContainerRef.indexOf(childViewRef);
viewContainerRef.remove(index);

I tried using a solution similar to the one proposed here to retrieve the child ViewRef: Angular2 , How to find index of a view inside a viewContianer

But the indexOf is returning -1, as if the child ViewRef is not present in the container.

I wanted to remove these child components outside of ParentComponent, not inside the ParentComponent code, like removing from the items array or through bindings.

I tried using the destroyView method from the Renderer also, but with no effect. The NgFor is iterating through the items array and creating embedded views for each childTwo, I don't if, in this case, the ViewRef obtained from ChildTwo injector (as proposed in the link solution) is the same as these EmbeddedViewRef.

Is there any way to do something like that?

Thank you.

*********************** UPDATE ***************************

I will try to simplify a little what I am trying to achieve. I am developing a dashboard where the user can create its components using an API like the following:

@Component({ selector: 'child' })
class UserChildComponent extends DashboardComponent {
  (...)
}

@Component({ selector: 'parent' })
class UserParentComponent extends DashboardComponent {

  @ViewChildren(UserChildComponent) m_children: QueryList<UserChildComponent>;

  private models: Array<ChildModel>;

  (...)
}

/* UserParentComponent template
<div>
  <child></child>
  <child *ngFor="let model of models"></child>
</div>

I have a logical tree of all the Dashboard components and its relationships with one another (parent/child), because of the subtyping and other stuff behing the scenes that doesn't concern here.

This is the scenario I am thinking of: the user would be able to remove any of these child dashboard components. I would like to, having all the relationship tree needed, remove the child from the parent and notify the user (maybe through the ViewChildren in the parent). Or I could actually call a method on the parent, so that the user could update its models Array, maybe would be the best way.

I was just thinking of a way that the user would have the less work possible, not having to worry about synchronizing the model\component (in this case)

Thanks again.

Community
  • 1
  • 1
  • 1
    What are you actually trying to accomplish? What element or component do you want to remove and why? – Günter Zöchbauer Dec 02 '16 at 11:59
  • Exactly, Cause removing elements is normally a job Angular does through directives – Reyraa Dec 02 '16 at 12:01
  • I want to be able to remove any child component of a component from the outside. @GünterZöchbauer , i wanted a generic way to remove any child from any parent component from the outside. This would be the best way implement what I want. – Daniel Seibel Silva Dec 02 '16 at 12:10
  • @alihaghighatkhah, yeah, that's what I thought so, viewing the ngFor code, for example, its creating and removing the embbeded views itself. But isn't there any way to remove any child view from the parent, independent where it was created? – Daniel Seibel Silva Dec 02 '16 at 12:13
  • you can write method/listener in parent component which removes whatever elements, and fire(remove) event from child – anshuVersatile Dec 02 '16 at 12:19
  • @anshuVersatile, could you elaborate a little more? I don't know if I understood correctly. The way you say, I would need the create a remove event for each child type, and handle this on the parent, is that what you said? – Daniel Seibel Silva Dec 02 '16 at 12:30
  • yes @DanielSeibelSilva – anshuVersatile Dec 02 '16 at 12:31
  • 1
    What about an approach like demonstrated in http://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 and the user only has to update the model and doesn't have to worry about synchronizing the DOM. That's how it's usually done in Angular2. – Günter Zöchbauer Dec 02 '16 at 13:03
  • 1
    @Daniel, Angular is aimed to manipulate the DOM through data. This is wrong approach because you're trying to manipulate DOM directly without changing the data. Even if you implement this wrapper it doesn't change the situation - you just delegate DOM manipulation to another component which still goes against Angular's ideology. So what I'd recommend is: a) remove items from array, and Angular will do the rest; b) another components that need to be removed wrap in *ngIf with flag - your approach also requires explicit placing custom directives, so use standard ones first. – Alexander Leonov Dec 02 '16 at 13:15
  • @GünterZöchbauer, yeah, I was thinking about that. I agree that this is the angular 2 way of doing it. I was thinking of a solution where the user wouldn't have to worry with stuff like that. But since the user is adding to the array of model, he might as well removed it too. Thanks for the link. Actually I was reading about this [NgComponentOutlet](https://github.com/angular/angular/pull/11235) Very interesting, but I don't know if it would have any utility in this case, anyway. :) – Daniel Seibel Silva Dec 02 '16 at 13:35
  • @AlexanderLeonov, thanks, I may as well do that. I think that custom directives are not the problem, being part of the "dashboard developer API", as long as it simplify the work to be done. :) – Daniel Seibel Silva Dec 02 '16 at 13:37
  • @AlexanderLeonov that's not true. The wrapper doesn't do direct DOM manipulation. It uses `ViewContainerRef.createComponent()` which is a perfectly fine API to use. It's also what the router uses to add/remove routed components. – Günter Zöchbauer Dec 02 '16 at 13:37
  • @Günter, formally - yes, it's perfectly fine API. But what does it do? It manipulates view. But doing this without changing the data - that's what I consider wrong. The problem in the main post does not sound like something requiring custom directive like ngIf or ngFor, thus it should not manipulate view directly. That was the main point. – Alexander Leonov Dec 02 '16 at 14:29
  • Using `*ngFor` would be equally false. It's also just a directive that manipulates the DOM. – Günter Zöchbauer Dec 02 '16 at 14:31
  • @Günter, This is why now I say 'view', not 'DOM'. :) I agree, DOM was not proper term. View is. – Alexander Leonov Dec 02 '16 at 14:33

2 Answers2

1

You can use

let element = querySelector(...);
element.parentNode.removeChild(element)

But it's discouraged to use direct DOM access in Angular2. Angular2 itself doesn't provide an API for what you want. In Angular2 you should just update the model and let Angular do the DOM manipulation.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Yeah, I wanted to avoid using the DOM directly, because of that. Also, doing this would call the ngOnDestroy of the removed component? Thanks, Gunter! – Daniel Seibel Silva Dec 02 '16 at 12:29
  • Why don't you want to it to be destroyed when you remove it? – Günter Zöchbauer Dec 02 '16 at 12:30
  • Actually, I want to be called. ;) Was just confirming if it was actually called when removing it directly from the DOM. Thanks, but I will have to find another way to not operate using the DOM. :( – Daniel Seibel Silva Dec 02 '16 at 12:37
  • Without knowing your exact use case it sounds strongly like something you shouldn't do at all. Perhaps if you could share some information about what you actually try to accomplish I might be able to give some pointers. – Günter Zöchbauer Dec 02 '16 at 12:39
  • Well , this might work, but you might as well use JQuery . – Milad Dec 02 '16 at 12:43
  • @GünterZöchbauer, I updated the question with an explanation of what is my scenario. Thanks again. – Daniel Seibel Silva Dec 02 '16 at 13:00
  • @GünterZöchbauer, What should be angular equivalent code for $('.abc').children().not('.cde, .dec, .fgh').remove(); – Mahi Sep 17 '18 at 06:59
  • @Ahmadmnzr There is no equivalent. – Günter Zöchbauer Sep 19 '18 at 12:46
  • @Günter Zöchbauer, it means , the above functionality can't be achieved in angular 2+ like you did in your above answer. – Mahi Sep 19 '18 at 12:49
  • Sure, it can be achieved. Your jQuery example code also uses only browser capabilities. There is just no special support for that provided by Angular. You can use everything the browser provides to achieve that though. For example use a loop to filter the received result to get the `.not()` equivalent. – Günter Zöchbauer Sep 19 '18 at 12:52
  • Thanks @Günter Zöchbauer, I will try as suggested. – Mahi Sep 19 '18 at 13:23
1

One possible solution that I can think of is to create a Service which has an event emitter like this :

export class ChildRemoveNotifierService{

     public $remove = new EventEmitter<string>();

}

And then , we'd need a directive that can remove element and create them as well :

 @Directive({ selector: '[remover]' })
export class RemoveDirective {

  @Input remove = '';
  constructor(
    private removerService:ChildRemoveNotifierService ,
    private _templateRef: TemplateRef,
    private _viewContainer: ViewContainerRef
    ) {


          let subscription =
          this.removerService.$remove.subscribe((tobeRemoved)=>{
             if(tobeRemoved===this.remove){
                 this._viewContainer.clear();
                 subscription.unsubscribe();
             }
          })
    }

And then , you'd use this like this:

 <span *remover='my-component'>
     <my-component></my-component>
 </span>

Basically , any component that needs to be removed dynamically , should be wrapped by *remover directive.

And then , now, everyOne can remove everyone :)

You parent component can fire an event like this :

export class ParentComponentOrAnyOther{

     constructor(private removerService:ChildRemoveNotifierService){

      }

      removeAComponent(){

          removerService.$remove.emit('my-component');

      }
}

This is not ideal , or maybe it is , I don't know.

But I do know that this is absolutely the Angular way and your not doing any DOM manipulation .

This way , you have all the Angular2 life cycles , specially onDestroy event will be fired inside my-component.

EDIT :

I think this would work too :

  <my-component *remover='my-component'></my-component>
Milad
  • 27,506
  • 11
  • 76
  • 85
  • this is an interesting approach, indeed. Wrapping the removable components in another element. If I understood this correctly, in your example, there would always be a element leftover when the child is removed. Not that this would be too much of a worry. Thanks for the trouble of writing this. :) – Daniel Seibel Silva Dec 02 '16 at 13:04
  • Yeah , that's why I said it might not be absolutely nice , but the problem is if you don't load a component dynamically yourself and let Angular2 load it normally , you won't be able to destroy it – Milad Dec 02 '16 at 13:06
  • Actually , I think you can put *remove on the my-component itself as well!!! , there wouldn't be any span left I think – Milad Dec 02 '16 at 13:08
  • Why not just use ngIf? – Alexander Leonov Dec 02 '16 at 13:18
  • -ngIf would only work if you have direct access to the variable that is setting it to true or false , so it might be difficult if you want to make an ngIf false from a parent component. -ngIf would create the component back if it's true , this question doesn't want this. -This way even the directive can fire the event to remove other components. -He'd probably need to put more stuff inside the directive. -The whole subscribtion idea would not work with ngIf , unless the component who has the ngIf would subscribe to something which is hard to track if you have multiple ngIf in one component – Milad Dec 02 '16 at 13:24
  • @xe4me, I think the ngIf wouldn't apply here, like you said. Thanks. ;) – Daniel Seibel Silva Dec 02 '16 at 13:41
  • @xe4me, to me the problem here is not in 'how' to manipulate someone's view. The problem is 'why do you need this at all'. It should be something very unique and unusual to require this. Working with some dashboards does not look like it. Direct manipulation someone else's child's view from somewhere outside sounds like a big design mistake to me. Also, removing someone's view without removing component itself... Again, there's something wrong here. This problem just should not happen at all. If it happens - then you're doing something wrong somewhere else and that should be addressed first. – Alexander Leonov Dec 02 '16 at 15:26
  • @AlexanderLeonov, I understand your point. The component child models\contexts and child views should be synchronized somehow. If I remove the view directly, there could arise a situation where a model exist and it's view not anymore. I don't know how the ngFor would behave in this case, if it would recreate the view. At first I thought that the user could synchronize the model using the ViewChildren.changes, but it seems to be going reverse of what should be done. The user should worry only with the model itself. There seems to be something wrong doing this way, like you said. Thanks. – Daniel Seibel Silva Dec 02 '16 at 18:29
  • @DanielSeibelSilva, You got it right. It just doesn't feel natural to go in such a deep mechanics of Angular to do something so trivial as dashboards showing/hiding (as it got it from your explanations). If you could give us a bigger picture of what you're trying to achieve by doing this then we could probably help you with finding simpler way of doing it. – Alexander Leonov Dec 02 '16 at 22:34