How do you access the for loop of the parent component within a child component which has the inputs?

Viewed 2126

So I am stuck on this. I am trying to get the Parent component to talk or integrate with the child component. Here is the parent component which basically has the for loop used to iterate or generate more links if a user wants to add more or presses the button to add more.

<div class="section url-wrapper">
    <div *ngFor="let url of urls; let i = index;" class="link input-wrapper">
            <childComponent></childComponent>
      <button class="button bad icon-only" (click)="removeURL(i)">
        <i class="far fa-times"></i>
      </button>
    </div>
  </div>

The parent component should only be able to register and display the output of the child component.

This is an example of the child component

<div class="section url-wrap">
    <input aria-label="URL Title" placeholder="Title" type="text" [value]="urls[i].title" (ngModel)="urls[i].title" name="url.title.{{ i }}"
        (input)="updateTitle(i, $event.target.value)">

          <input aria-label="URL" placeholder="https://example.com" type="text" [value]="urls[i].url" (ngModel)="urls[i].url" name="url.url.{{ i }}"
          (input)="updateUrl(i, $event.target.value)">   
 </div>

I need help both allowing the parent component to register input from the child component and being able to iterate from the for loop from the parent if it is possible.

Please let me know if you need more information such as the component files or clarification

3 Answers

The below code & example will demonstrate how data flows from parent -> child -> parent by using the @Input() and @Output() directives.

Working Example Here

parent.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <div class="section url-wrapper">
      <div *ngFor="let url of urls" class="link input-wrapper">
        <app-child [url]="url" (updateUrl)="onUrlUpdate($event)"></app-child>
      </div>
    </div>
  `
})
export class ParentComponent implements OnInit {
  public urls = [
    {url: "https://example.com", title: "Example1"},
    {url: "https://example.com", title: "Example2"},
    {url: "https://example.com", title: "Example3"},
  ]
  constructor() { }

  ngOnInit() {

  }

  onUrlUpdate($event) {
    // completely overkill, but just used to demonstrate a point
    var url = this.urls.find(_url => {
      // we can see here that the $event.url is actually the same object as the urls[i] that was
      // passed to the child. We do not lose the reference when it is passed to the child or back
      // up to the parent. 
      return $event.url === _url
    });
    if (url) {
      url[$event.prop] = $event.newValue;
    }
    console.log(`Updated URL's "${$event.prop}" property with the value "${$event.newValue}"`);
  }

}

child.component.ts

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
  <div class="section url-wrap">
    <input aria-label="URL Title" placeholder="Title" type="text" [value]="url.title"
        (input)="handleUrlUpdate($event, 'title')"/>

    <input aria-label="URL" placeholder="https://example.com" type="text" [value]="url.url"
        (input)="handleUrlUpdate($event, 'url')"/>   
 </div>
  `,
})
export class ChildComponent implements OnInit {
  @Input() url; // passed in from parent via [url] property on <app-child>
  @Output() updateUrl = new EventEmitter();
  constructor() { }

  ngOnInit() {
    // this.url is now available for the life of the child component (assuming it was passed by the parent)
  }

  handleUrlUpdate($event, propToUpdate) {
    // overkill, but used to demonstrate a point
    this.updateUrl.emit({url: this.url, prop: propToUpdate, newValue: $event.target.value});
  }

}

The stardard way to let components speack each others is with input-output:

You can pass values from parent to children with @Input for example:

Parent code:

<childComponent [someInputValue]="hello"></childComponent>

Children code:

@Input() someInputValue; //this property will be "hello"

and you can pass values (after being triggered) from children to parent:

Children code:

  @Output() itemSelectedOutput: EventEmitter<any> = new EventEmitter();

  buttonClicked() {
   this.itemSelectedOutput.emit("clicked");
  }

Parent code:

    <childComponent [someInputValue]="hello" (itemSelectedOutput)="someParentMethod($event)"></childComponent>

someParentMethod(event: any) {
 console.log(event);
}

You can reach the same thing with ISubscription but I suggest you to use the way above

Hope it can help

I wouldn't do it this way in particular. If the children have to know about the parents, then your architecture should be adjusted

Related