5
votes

I have an Item container and item pattern where the child items shoud be added and removed from its container. Adding is fine while remove does nothing. It seems that the angular2 *ngFor directive does not work when any of the child items removed.

    import { NgFor} from 'angular2/common';
    import { bootstrap } from 'angular2/platform/browser';
    import { Component, View, Directive, OnDestroy, Input, enableProdMode } from 'angular2/core';
    import { CORE_DIRECTIVES} from 'angular2/common';


    @Component({selector: 'itemcontainer',})
    @View({ template: `<ul (click)="$event.preventDefault()">
                       <li *ngFor="#it of items">Any Item</li>
                       </ul>
                       <div><ng-content></ng-content></div>`,

            directives: [NgFor],
    })
    export class ItemContainer {
        public items: Array<Item> = [];

        public addItem(item: Item) {
            this.items.push(item);
        }

        public removeItem(item: Item) {
            var index = this.items.indexOf(item);
            if (index === -1) {
                return;
            }

            console.log(`Index about to remove: ${index} this.items length: ${this.items.length}`);
            this.items.slice(index, 1);
            console.log(`this.items length: ${this.items.length}`);
        }
    }

    @Directive({ selector: 'item' })
    export class Item implements OnDestroy {

        @Input() public heading: string;

        constructor(public itemcontainer: ItemContainer) {
            this.itemcontainer.addItem(this);
        }

        ngOnDestroy() {
            this.itemcontainer.removeItem(this);
        }
    }

    @Component({
        selector: 'my-app'
    })
    @View({
        template: `<div (click)="$event.preventDefault()">
            <button type="button" (click)="addItem()">Add item</button>
            <button type="button" (click)="removeItem()">Remove item</button>

        <itemcontainer>
            <item *ngFor="#containerItem of containerItems" [heading]="containerItem.title">Content </item>
        </itemcontainer>
    </div>`,

        directives: [CORE_DIRECTIVES, Item, ItemContainer],

    })

    class Tester {

        private counter: number = 2;

        public containerItems: Array<any> = [
            { title: 'Item1' },
            { title: 'Item2' },
        ];

        addItem() {
            this.containerItems.push({ title: `Item ${this.counter}` });
        }

        removeItem() {

            if (this.containerItems.length > 0) {
                this.containerItems.splice(this.containerItems.length - 1, 1);
            }
        }
    }

    enableProdMode();
    bootstrap(Tester);

Here is the DOM look like after two new items added and removed:

    <itemcontainer>
        <ul>
            <li>Any Item</li>
            <li>Any Item</li>
            <li>Any Item</li>
            <li>Any Item</li>
        </ul>
        <div>
            <item>Content </item>
            <item>Content </item>
        </div>
    </itemcontainer>

The issue is the li part does not removed. Any idea?

(I tested it with angular 2.0.0-beta.3 and 2.)

5

5 Answers

3
votes

In fact, Angular2 only detects changes when the instance changes. I mean the instance of the array not changes internally on elements.

You could use this (see the second slice call):

public removeItem(item: Item) {
  var index = this.items.indexOf(item);
  if (index === -1) {
    return;
  }

  console.log(`Index about to remove: ${index} this.items length: ${this.items.length}`);
  this.items.slice(index, 1);
  console.log(`this.items length: ${this.items.length}`);

  this.items = this.items.slice();
}

This answers could also help you:

13
votes

You need to use splice() not slice(). There are no issues with Angular change detection here.

this.items.splice(index, 1);

NgFor will loop over your array items, and it will detect when something is added or removed just fine.

Plunker

Also, you can remove this stuff:

import { NgFor} from 'angular2/common';
import { CORE_DIRECTIVES} from 'angular2/common';

     directives: [NgFor],

Also, you have circular references in your data. Change your code to the following

<li *ngFor="#it of items">Any Item {{it | json}}</li>

and note the error in the console:

EXCEPTION: TypeError: Converting circular structure to JSON in [Any Item {{it | json}} in ItemContainer

1
votes

Yes, it is change detection indeed. This what worked for me:

    this.items = [
        ...this.items.slice(0, index),
        ...this.items.slice(index + 1, this.items.length)
    ];
0
votes

The problem probably has to do with not setting forward references properly (i.e. you shouldn't use a class before its declared). To address the issue, you can use a shared service:

export class SharedService {
    public items: Array<Item>=[];

    public addItem(item:Item) {
      this.items.push(item);
    }

    public removeItem(item:Item) {
       var index = this.items.indexOf(item);
      if (index >=0) {
        this.items.splice(index,1);
      }
    }
}

Use the shared service in your Item constructor/destructor:

@Directive({ selector: 'item' })
export class Item implements OnDestroy {

    @Input() public heading: string;

    constructor(public sharedService:SharedService) {
        this.sharedService.addItem(this);
    }

    ngOnDestroy() {

        this.sharedService.removeItem(this);

    }
}

And also in your ItemContainer:

@Component({selector: 'itemcontainer',})
@View({ template: `<ul (click)="$event.preventDefault()">
                   <li *ngFor="#it of items">Any Item </li>
                   </ul>

                   <div><ng-content></ng-content></div> `,

        directives: [NgFor],
})
export class ItemContainer {
    public items: Array<Item> = [];
    constructor(public sharedService:SharedService) {
       this.items = this.sharedService.items;
    }


}

Demo Plnkr

-1
votes

I am having the same issue with you, even I used splice or detecChange it was still not working @McLac provided the method of reform array like this

this.items = [
    ...this.items.slice(0, index),
    ...this.items.slice(index + 1, this.items.length)
];

saved my life. It works perfect. I have my code like this:

let idx = this.prds.indexOf(prd);
if(idx==-1){
  return
};
/*    it was like this before, consol.log show deleted array correctly just the UI has no change..
for(let i =0; i< this.prds.length; i++){
  if(this.prds[i]==prd){
    this.prds.splice(i,1);
    break;
  }
}
*/
this.prds = [
  ...this.prds.slice(0,idx),
  ...this.prds.slice(idx+1,this.prds.length)
];