Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 - ngFor directive does not map the removed item

Tags:

angular

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

like image 877
McLac Avatar asked Feb 12 '16 12:02

McLac


3 Answers

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

like image 177
Mark Rajcok Avatar answered Nov 05 '22 13:11

Mark Rajcok


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:

  • Angular 2: Trouble with Custom Components
  • Angular2 @ViewChildren/@ViewQuery's QueryList not updated when dynamically adding second level children
like image 35
Thierry Templier Avatar answered Nov 05 '22 12:11

Thierry Templier


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)
    ];
like image 1
McLac Avatar answered Nov 05 '22 14:11

McLac