Angular 2 - Передача данных дочернему компоненту после инициализации родителя

У меня есть родительский компонент, который получает данные с сервера через службу. Мне нужно, чтобы некоторые из этих данных передавались дочернему компоненту.

Я пытался передать эти данные с обычным @Input(), однако, поскольку я ожидал, что данные, которые я получаю в дочернем компоненте, undefined.

Пример

Родительский компонент

@Component({
    selector: 'test-parent',
    template: `
        <test-child [childData]="data"></test-child>
    `
})

export class ParentComponent implements OnInit {
    data: any;

    ngOnInit() {
        this.getData();
    }

    getData() {
        // calling service and getting data
        this.testService.getData()
            .subscribe(res => {
                this.data = res;
            });
    }
}

Детский компонент

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnInit {
    @Input() childData: any;

    ngOnInit() {
        console.log(childData); // logs undefined
    }
}

Я сохранил пример простым, чтобы показать, что я пытаюсь сделать. Мой вопрос в том, можно ли передавать данные ребенку после инициализации. Я не уверен, но в этом случае поможет двусторонняя привязка данных?

Больше исследований

Следуя опубликованным ответам, я попытался использовать крюк ngOnChanges lifecycle. Проблема, с которой я сталкиваюсь, заключается в том, что функция ngOnChanges не запускается, когда данные обновляются. Данные являются объектом, поэтому я считаю, что изменения не обнаруживаются.

Обновлен код в дочернем компоненте

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnChanges {
    @Input() childData: any;

    ngOnChanges() {
        console.log(childData); // logs undefined
    }
}

Я пробовал тест с Live-примерами на Plunker из команды Angular, демонстрирующей привязки Lifecycle. В тесте я обновил OnChangesComponent для передачи объекта, который, как видно из примера при обновлении объекта, ngOnChanges не обнаруживает изменения. (Это также показано в этом question)

https://plnkr.co/edit/KkqwpsYKXmRAx5DK4cnV

Кажется, что ngDoCheck может помочь решить эту проблему, но мне кажется, что это не поможет производительности в долгосрочной перспективе, поскольку каждое изменение обнаружено этим Lifecycle Hook.

Также я знаю, что, возможно, я мог бы использовать @ViewChild() для доступа к дочернему компоненту и установить нужные мне переменные, но я не думаю, что для этого варианта использования это имеет смысл.

Проводка большего количества кода, чтобы лучше объяснить

Родительский компонент

@Component({
    selector: 'test-parent',
    template: `
        <test-child [childType]="numbers" [childData]="data" (pickItem)="pickNumber($event)">
            <template let-number>
                <span class="number" [class.is-picked]="number.isPicked">
                    {{ number.number }}
                </span>
            </template>
        </test-child>
        <test-child [childType]="letters" [childData]="data" (pickItem)="pickLetter($event)">
            <template let-letter>
                <span class="letter" [class.is-picked]="letter.isPicked">
                    {{ letter.displayName }}
                </span>
            </template>
        </test-child>
        <button (click)="submitSelections()">Submit Selection</button>
    `
})

export class ParentComponent implements OnInit {
    data: any;
    numbersData: any;
    lettersData: any;

    ngOnInit() {
        this.getData();
    }

    getData() {
        // calling service and getting data for the game selections
        this.testService.getData()
            .subscribe(res => {
                this.data = res;
                setChildData(this.data);
            });
    }

    setChildData(data: any) {
        for(let i = 0; i < data.sections.length; i++) {
            if(data.sections[i].type === 'numbers') {
                this.numbersData = data.sections[i];
            }

            if(data.sections[i].type === 'letters') {
                this.lettersData = data.sections[i];
            }
        }
    }

    pickNumber(pickedNumbers: any[]) {
        this.pickedNumbers = pickedNumbers;
    }

    pickLetter(pickedLetters: any[]) {
        this.pickedLetters = pickedLetters;
    }

    submitSelections() {
        // call service to submit selections
    }
}

Детский компонент

@Component({
    selector: 'test-child',
    template: `
        <!--
            Content for child...
            Showing list of items for selection, on click item is selected
        -->
    `
})

export class ChildComponent implements OnInit {
    @Input() childData: any;
    @Output() onSelection = new EventEmitter<Selection>;

    pickedItems: any[];

    // used for click event
    pickItem(item: any) {
        this.pickedItems.push(item.number);

        this.onSelection.emit(this.pickedItems);
    }
}

Это более или менее код, который у меня есть. В основном у меня есть родитель, который обрабатывает выбор из дочерних компонентов, а затем я отправляю эти варианты в службу. Мне нужно передать определенные данные ребенку, потому что я пытаюсь получить объект, который возвращает ребенок в том формате, который ожидает служба. Это поможет мне не создавать весь объект, ожидаемый службой в родительском компоненте. Кроме того, это сделает ребенка повторно используемым в том смысле, что он отправит обратно то, что ожидает служба.

Обновление

Я ценю, что пользователи по-прежнему публикуют ответы на этот вопрос. Обратите внимание, что код, который был выше, работал у меня. Проблема была в том, что в конкретном шаблоне у меня была опечатка, которая привела к тому, что данные были undefined.

Надеюсь, что это окажется полезным:)

Ответы

Ответ 1

Поскольку данные начинаются с undefined, вы можете отложить его с помощью * ngIf = 'data'

<div *ngIf='data'>
   <test-child [childData]="data"></test-child>
</div>

Или вы можете реализовать ControlValueAccessor на своем компоненте и передать его ngModel с помощью ngModelChange

<test-child [ngModel]="data?" (ngModelChange)="data? ? data= $event : null"></test-child>

Ответ 2

вы можете использовать ngOnChanges для получения данных, как показано ниже,

 ngOnChanges() {
   if(!!childData){         
        console.log(childData);         
   }
 }

OnChanges

Angular вызывает свой метод ngOnChanges всякий раз, когда он обнаруживает изменения в входные свойства компонента (или директивы).

Подробнее об этом здесь.

Обновление

ngOnChanges будет срабатывать только при изменении всего объекта, если вы хотите просто обновить свойство связанного объекта, а не целого объекта, у вас есть два варианта,

  • либо привязать свойство непосредственно в шаблоне, обязательно используйте ?, например {{childData?.propertyname}}

  • или вы можете создать свойство get в зависимости от объекта childdata,

    get prop2(){
      return this.childData.prop2;
    }
    

Пример Copmlete,

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

@Component({
  selector: 'my-app',
  template: `<h1>Hello {{name}}</h1>
  <child-component [childData]='childData' ></child-component>
  `
})
export class AppComponent { 
  name = 'Angular'; 
  childData = {};
  constructor(){
    // set childdata after 2 second
    setTimeout(() => {
      this.childData = {
        prop1 : 'value of prop1',
        prop2 : 'value of prop2'
      }
    }, 2000)
  }
}

@Component({
  selector: 'child-component',
  template: `
    prop1 : {{childData?.prop1}}
    <br />
    prop2 : {{prop2}}
  `
})
export class ChildComponent { 
  @Input() childData: {prop1: string, prop2: string};

  get prop2(){
    return this.childData.prop2;
  }
}

Вот Plunker!

Надеюсь, это поможет!

Ответ 3

В то время, когда ваш ChildComponent (вы назвали его также ParentComponent, но я предполагаю, что это опечатка) инициализируется и выполняет свой ngOnInit, в вашем родителе нет данных, поскольку ваша служба все равно будет получать ваши данные. Как только ваши данные будут там, они будут перенесены на ребенка.

Что вы можете сделать, зависит от вашего использования.

Если вы просто хотите отображать данные в шаблоне ChildComponent , вы можете использовать оператор elvis (?). Что-то вроде: {{ childData?}} или {{ childData?.myKeyName }}...

Если вы хотите сделать что-то еще, вы можете использовать setter в своем ChildComponent. Он перехватит входное изменение и даст вам возможность изменять/тестировать/ "делать то, что когда-либо захочет" с ним, прежде чем делать что-то с ним в вашем ChildComponent.. например.:

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnInit {
    private _childData: any;
    @Input()
    set childData(parentData: any) {
        // every time the data from the parent changes this will run
        console.log(parnetData);
        this._childData = parentData; // ...or do some other crazy stuff
    }
    get childData(): any { return this._childData; }

    ngOnInit() {
      // We don't need it for that...
    }
}

или используйте ngOnChanges, о чем упомянул Мадху.

Вы также можете взглянуть на запись поваренной книги для Компонентная связь в официальных документах.

Ответ 4


   '@Component({
    selector: 'test-child',
    template: '
        
    '
})

export class ChildComponent implements OnChanges {
    @Input() childData: any;

    ngOnChanges() {
        console.log(childData); // logs undefined
    }
}'

Это должно быть так? Нет? Надеюсь, это может быть проблемой

'ngOnChanges() {       console.log(this.childData);//ссылка с этим

}'

Ответ 5

Я предполагаю, что ребенок получает подмножество данных родителей, потенциально из действия пользователя (например, загрузка формы, когда пользователь нажимает на строку сетки). В этом случае я бы использовал событие.

У родителя загорится событие, возможно, выбрано значение DataSelected, а содержимое события будет выбрано.

Затем ребенок прослушивает эти события и при запуске загружает данные из события в модель.

Это поможет разделить ваши компоненты, что всегда является хорошей идеей.