popClingwrap
popClingwrap

Reputation: 4217

Create child components with NgFor then call a method of whichever emits an event

I can think of a few ways that I might hack this but I'd like some advice on the most 'Angular' way of doing it.

I've put together an example of what I'm doing.

The main component uses NgFor to iterate over a list and create a bunch of child components.
Each child emits an event when clicked which is picked up by the parent. Each child also has a method update().

What I want is the correct way for my main component to get a reference to to whichever child emits an event and call its update() method.

Seems pretty simple (probably is pretty simple) but it is quite a central feature of the app I'm building so I want to do it as nice and clean as possible.

Cheers for you help

Upvotes: 5

Views: 3095

Answers (2)

Mateusz Kocz
Mateusz Kocz

Reputation: 4602

Since Angular 2 embraced the one-way data flow paradigm, I would argue that the "most 'Angular' way" to solve this problem is to not call the update() method at all. The state of child components should be provided by the parent.

Whenever the click happens, the new state should be computed in the App (parent) component and will be passed down to the ChildComponent.

Going this way, you will need to extend the state that is held in the parent, by an additional property (eg. clicked):

rowList = [{name: "Alice", clicked: false}, ...];

(Of course, since undefined has the same logical value as false, explicit declaration is not really necessary.)

On click, in the handler method, the new state is computed. In this case, it's just a simple boolean value change:

processClick(id){
  this.recentClick = id;
  // Create the new array for immutability benefits.
  this.rowList = this.rowList.map(person => {
    if (person.name === id) {
      return {name: person.name, clicked: true};
    } else {
      return person;
    }
  })
}

The value is passed down to the child, as any other @Input.

<my-child *ngFor="let row of rowList;" [id]=row.name [clicked]=row.clicked (onMD)="processClick($event)"></my-child>

Now, the child component can change it's behavior depending on the value of the clicked property. As a bonus, when using immutable data, you can change the change detection strategy to onPush for some performance benefits.

@Component({
    // ...
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() clicked: boolean;

  get prefix() {
    return this.clicked ? "Bye!" : "Hi";
  }
  // ...
}

There are multiple benefits of this approach.

  • You don't have a hidden state lurking in child components. Parent's "word" is absolute (so called "single source of truth").
  • The parent doesn't really need to know or care about what children do with the data or when they need to update.
  • You cal easily extract the data somewhere else, and you won't need to write any code making the update() behavior works.

The full implementation would look like this: http://plnkr.co/edit/IP8iLKxKGCjHOdgPHJ0h?p=preview

Upvotes: 4

JB Nizet
JB Nizet

Reputation: 691943

The simplest way is to use a template variable to reference the child component:

<my-child #child *ngFor="let row of rowList;" [id]="row.name"
          (onMD)="processClick($event); child.update()"></my-child>

Updated demo: http://plnkr.co/edit/zA80iNsLHHULGx9Rbffw?p=preview

Upvotes: 5

Related Questions