Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ngFor not update when addition to array comes from another component?

Tags:

angular

I've been at this for some time, many articles, the documentation, much trial and error but I feel I'm missing something core.

Essentially I'm trying to create an extensible navigation component that other components can add items to when they are added to the page. I've tried to accomplish this a couple ways, including a service, this example I'm attempting it by injected one component into another component.

I have a component with a list of items in it, I have an ngFor that loops through the items and displays text. I have a button on the page that when you click adds an item to the array. I also have injected the component into another component that I have add an item to the array on NgOnInit() (tried other lifecycle events and in the constructor).

The strangeness is that when the button adds the items to the array the list updates but when the other component adds the items the list it does not update the UI even though the count of items is incremented and I can see in the UI the component has already loaded and rendered the default items.

import {Component} from 'angular2/core'; 
import {Router, RouteParams} from 'angular2/router'; 
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; 
import {Injectable} from 'angular2/core';

@Component({
    selector: 'caseBasedSearchSidebar',
    template: `<ul id="sidebar-wrapper" class="clearfix nav navbar-default sidebar-nav">
                  <li *ngFor="#nav of navigationItems">
                    <span>{{nav.name}}</span>
                  </li>
           <ul>  <button (click)=addNavigationItem()>Add</button> `,
directives: [ROUTER_DIRECTIVES] })

@Injectable() export class SidebarComponent {
    public navigationItems: Array<ISideNavigationItem>;

    constructor() {
        this.navigationItems = [{ name: 'test' }];
    }

    public addNavigationItem(item: ISideNavigationItem) {
        this.navigationItems.push({ name: 'test' });
    } 
}

export interface ISideNavigationItem {
name: string; }

import {Component, OnInit} from 'angular2/core'; 
import {SidebarComponent} from '../sideBar.component';

@Component({
    templateUrl: '/CaseBasedSearch/PatientInformation',
    providers: [SidebarComponent] 
})
export class PatientInformationComponent implements OnInit {
    private sideBarComponent: SidebarComponent;

    constructor(sideBarComponent: SidebarComponent) {
        this.sideBarComponent = sideBarComponent;
    }

    ngOnInit() {
        this.sideBarComponent.addNavigationItem({ name: 'testing' });
    } 
}

Any guidance is appreciated.

like image 240
Honorable Chow Avatar asked Oct 31 '22 11:10

Honorable Chow


2 Answers

You are assigning the navigationItems model inside of your SidebarComponent. Changes to this model won't propagate to other components.

Consider setting up navigationItems as an @Input property instead:

@Input() public navigationItems: Array<ISideNavigationItem>;

And passing your model from the parent component (PatientInformationComponent) to your child component (SidebarComponent):

<case-based-search-sidebar [navigationItems]="navigationItems">

Also, as a matter of convention, the selector in your SidebarComponent should be snake-case:

selector: 'case-based-search-sidebar'

As others have mentioned:

  • SidebarComponent is not a service. You shouldn't need to inject it into the constructor, nor should you need to add it to the providers property.
  • The @Injectable attribute for your component should be removed.
like image 68
pixelbits Avatar answered Nov 15 '22 06:11

pixelbits


Ultimately my problem was scoping of the injected service/component. I was using the providers item on each component and with the service this resulted in each service having it's own instance. When the components updated the service it was not updating the same array which previous tests led me to believe it was. My moving the injection to boot.ts they all began getting the same instance. Answer here.

The final code;

import {Component, OnInit} from 'angular2/core';
import {CaseBasedSearchNavigationService} from '../caseBasedSearchNavigation.service';
import {ISideNavigationItem} from '../caseBasedSearchNavigation.service';

@Component({
    templateUrl: '/CaseBasedSearch/PatientInformation'
})


export class PatientInformationComponent implements OnInit {
    private navigationService: CaseBasedSearchNavigationService;

    constructor(navigationService: CaseBasedSearchNavigationService) {
        this.navigationService = navigationService;

        this.navigationService.addNavigationItem({ name: 'testing' });
        this.navigationService.addNavigationItem({ name: 'testing' });
    }

    ngOnInit() {

    }
} 

import {Component, Input} from 'angular2/core';
import {Router, RouteParams} from 'angular2/router';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {CaseBasedSearchNavigationService} from './caseBasedSearchNavigation.service';
import {ISideNavigationItem} from './caseBasedSearchNavigation.service';

@Component({
    selector: 'case-based-search-sidebar',
    template: `<ul id="sidebar-wrapper" class="clearfix nav navbar-default sidebar-nav clearfix">
              {{navigationItems.length}}
               <li *ngFor="#nav of navigationItems">
                        <span>{{nav.name}}</span>
                      </li>
               <ul>`,
    directives: [ROUTER_DIRECTIVES]
})

export class SidebarComponent {
   public navigationItems: ISideNavigationItem[];

   constructor(navigationService: CaseBasedSearchNavigationService) {
        this.navigationItems = navigationService.navigationItems;

        navigationService.addNavigationItem({name : 'test2'});
    }
}

import {Injectable} from 'angular2/core';

@Injectable()
export class CaseBasedSearchNavigationService {
    public navigationItems: ISideNavigationItem[];

    constructor() {
        this.navigationItems = [];
    }

    public addNavigationItem(item: ISideNavigationItem) {
        this.navigationItems.push(item);
    }
}

export interface ISideNavigationItem {
   name : string;
}
like image 31
Honorable Chow Avatar answered Nov 15 '22 07:11

Honorable Chow