【问题标题】:Angular 2 - Passing data to Child component after Parent initialisationAngular 2 - 在父初始化后将数据传递给子组件
【发布时间】:2017-05-01 08:35:41
【问题描述】:

我有一个父组件,它通过服务从服务器获取数据。我需要将其中一些数据传递给子组件。

我一直在尝试使用通常的 @Input() 传递这些数据,但是正如我所料,我在 Child 组件中获得的数据是 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 生命周期挂钩的发布答案。我遇到的问题是更新数据时没有触发ngOnChanges 函数。数据是一个对象,这就是我认为没有检测到更改的原因。

子组件中的更新代码

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

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

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

我已经尝试使用来自 Angular 团队的 Plunker 上的 Live Examples 进行测试,展示了 Lifecycle Hooks。在测试中,我更新了 OnChangesComponent 以传递一个对象,如示例中所示,在更新对象时 ngOnChanges 未检测到更改。 (这在question中也有展示)

https://plnkr.co/edit/KkqwpsYKXmRAx5DK4cnV

似乎ngDoCheck 可以帮助解决这个问题,但在我看来,从长远来看,这对性能没有帮助,因为这个生命周期挂钩会检测到每一个变化。

我也知道我可以使用@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

希望它对你有所帮助:)

【问题讨论】:

  • 在子组件中你应该通过引用 this.childData 来调用它
  • 我想这就是为什么它甚至在子组件中也没有加载

标签: angular angular-components


【解决方案1】:
@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

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

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

应该是这样的吧?我希望这可能是问题所在。

    ngOnChanges() {
        console.log(this.childData); // reference with this
    }

【讨论】:

    【解决方案2】:

    由于数据在开始时未定义,您可以使用 *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>
    

    【讨论】:

    • *ngIf='data' 在我看来不是最好的解决方案。对我来说它没有用。相反,ngOnChanges 工作正常
    【解决方案3】:

    您可以使用ngOnChanges 获取如下数据,

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

    OnChanges

    Angular 在检测到更改时调用其 ngOnChanges 方法 组件(或指令)的输入属性。

    阅读更多相关信息here

    更新

    ngOnChanges 只会在您更改整个对象时触发,如果您只想更新绑定对象的属性而不是整个对象,您有两个选择,

    1. 要么直接在模板中绑定属性,一定要像{{childData?.propertyname}}一样使用?

    2. 或者您可以根据 childdata 对象创建一个 get 属性,

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

    完整示例,

    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

    希望这会有所帮助!

    【讨论】:

    • 感谢您的回答,这似乎为我指明了正确的方向。我现在遇到的唯一问题是,如果数据是“简单”字符串,ngOnChanges 正在检测更改,但是如果由于某种原因它是一个对象,它会保留undefined
    • @DanielGrima:更新了答案。
    • 再次感谢您的帮助...我刚刚发现了问题所在。我的模板变量中有错字,所以它当然总是未定义。之前没有意识到!至少看起来它正在工作,显然我不需要任何 ngOnChanges 或对象的吸气剂..现在看起来还可以。
    【解决方案4】:

    当您的 ChildComponent(您也称它为 ParentComponent,但我猜这是一个错字)被初始化并执行其 ngOnInit 时,您的父级中没有可用的数据,因为您的服务仍将无法获取您的数据。一旦您的数据在那里,它将被推送给孩子。

    你能做什么取决于你的用例..

    如果您只想在您的 ChildComponent 模板 中显示数据,您可以使用 elvis 运算符 (?)。类似:{{ childData?}}{{ childData?.myKeyName }}...

    如果你想做一些其他的事情,你可以在你的 ChildComponent 中使用 setter。它将拦截输入更改,并让您有机会在您的 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 如 Madhu 所述。

    您可能还想查看官方文档中Component Communication 的食谱条目。

    【讨论】:

    • 是的,正如您提到的那样,这是一个错字 - 我已经用更多点更新了这个问题。我也尝试过你的建议,但是我仍然得到相同的结果:/
    • 您介意发布您的原始代码吗?我不是 100% 清楚为什么它对你不起作用......你可能想尝试的另一件事是 - 鉴于你没有在 ParentComponent 中使用来自服务的数据做任何其他事情 - 把你的 @ 987654327@ 直接进入您的子输入并让 Angular 使用 async 管道处理订阅。像这样:&lt;test-child [childData]="testService.getData() | async"&gt;&lt;/test-child&gt;
    • 我已经用更多代码更新了这个问题。希望它更清楚...感谢您的帮助!
    • 感谢您的帮助,正如我在上面评论的那样,我的问题是模板变量错字!令人沮丧的是我之前没有注意到它......现在似乎工作正常......再次感谢:)
    • 仅供参考:linting 将出现在 vs 代码中:) 当您在最新版本中使用 angular-cli 时,“ng lint”也会给您模板错误;)
    【解决方案5】:

    我假设孩子正在获取父母数据的子集,可能来自用户操作(例如当用户单击网格行时加载表单)。在这种情况下,我会使用一个事件。

    让父级触发一个事件,可能是 dataSelected,事件的内容就是被选中的数据。

    然后让孩子监听这些事件,并在触发时将事件中的数据加载到模型中。

    这将有助于解耦您的组件,这总是一个好主意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-30
      • 2019-08-25
      • 1970-01-01
      • 1970-01-01
      • 2021-02-23
      • 1970-01-01
      • 2017-08-04
      • 1970-01-01
      相关资源
      最近更新 更多