【问题标题】:@input variable is undefined when trying to print its values in ngOnit of the child component尝试在子组件的 ngOnit 中打印其值时,@input 变量未定义
【发布时间】:2020-11-09 10:45:58
【问题描述】:

我已经实现了一个 Angular 10 应用程序,它包含一个父组件 customer 和子组件 createcustomer。父组件包含一个列出所有客户的表格,单击表格上的编辑链接将数据传递给子组件。如您所见,调用了试图初始化@input 变量的父方法editCustomer。 目前我的子组件@Input 值未定义。我可以在父组件中看到对象的值。不知道为什么输入变量没有被初始化。有人可以解释一下吗

父组件

 import { Component, OnInit } from '@angular/core';
import { CustomerService } from '../../services/customer/customer.service';
import { ICustomer } from '../../models/customer.model';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CreateCustomerComponent } from '../create-customer/create-customer.component';

@Component({
  templateUrl: './customer.component.html',
  styleUrls: ['./customer.component.scss']
})
export class CustomerComponent implements OnInit {
  customerDetails: ICustomer[];
  customer: ICustomer;
  public modal: any;
  totalItems: number;
  maxSize = 5;
  term: string;
  isEdit: boolean;

  constructor(private customerService: CustomerService,
              public modalService: NgbModal) {
  }

  ngOnInit(): void {
    this.getCustomerDetails();
  }

  getCustomerDetails(): void {
    this.customerService.getCustomerDetails()
      .subscribe(data => {
        this.customerDetails = data;
        this.totalItems = data.length;
      }
      );
  }

  public addCustomer(): void {
    this.isEdit = false;
    this.openModal();
  }

  public editCustomer(custDetails: ICustomer): void {
   this.isEdit = true;
   this.openModal();
   this.customer = custDetails;
   console.log(this.customer);
  }

  private openModal(): void {
    this.modal = this.modalService.open(CreateCustomerComponent,
      { size: 'fullscreen', centered: true, backdrop: 'static' });
  }
}

父组件html

<div class="container">
  <div class="row">
    <div class="form-group">
      <input type="text" class="form-control" placeholder="Search Here" [(ngModel)]="term">
    </div>
    <div>
      <button class="btnAddCustomer" (click)="addCustomer()"> Add Customer (Template Driven) </button>
    </div>
  </div>
  <div class="row">
    <div class="col-12">
      <div *ngIf="customerDetails" style="height: 700px;  overflow-y:auto">
        <table id="customerdetails-table" class="table table-bordered table-striped">
          <thead>
            <th>Customer</th>
            <th>Company Name</th>
            <th>Contact Name</th>
            <th>Contact Title</th>
            <th>Address</th>
            <th>City</th>
            <th>Region</th>
            <th>Postal Code</th>
            <th>Country</th>
            <th>Phone</th>
            <th>Fax</th>
            <th>View Order</th>
          </thead>
          <tbody>
            <tr *ngFor="let custDetails of customerDetails">
              <td>{{custDetails.customerId}}</td>
              <td>{{custDetails.companyName}}</td>
              <td>{{custDetails.contactName}}</td>
              <td>{{custDetails.contactTitle}}</td>
              <td>{{custDetails.address}}</td>
              <td>{{custDetails.city}}</td>
              <td>{{custDetails.region}}</td>
              <td>{{custDetails.postalCode}}</td>
              <td>{{custDetails.country}}</td>
              <td>{{custDetails.phone}}</td>
              <td>{{custDetails.fax}}</td>
              <td>
                <a [routerLink]="'/customers'" class="table-row-action edit-action"  (click)="editCustomer(custDetails)">
                  edit
                </a>
              </td>
            </tr>
          </tbody>
        </table>

      </div>
      <pagination [totalItems]="totalItems" [itemsPerPage]="5" [maxSize]="maxSize"></pagination>

    </div>
  </div>
</div>


<app-create-customer [isEdit]="isEdit" [customer]="customer"></app-create-customer>

子组件

import { Component, OnInit, Input } from '@angular/core';
import { CustomerService } from '../../services/customer/customer.service';
import { ICustomer } from '../../models/customer.model';

@Component({
  selector: 'app-create-customer',
  templateUrl: './create-customer.component.html',
  styleUrls: ['./create-customer.component.scss']
})
export class CreateCustomerComponent implements OnInit {
  @Input() isEdit: boolean;
  @Input() customer: ICustomer;
  constructor(private customerDetailsService: CustomerService) { }

  ngOnInit(): void {
    console.log('Inside create customer component' + this.customer);
  }


}

【问题讨论】:

  • 如果将 isEdit 设置为 false 或 true 会发生什么?我的意思是在声明的同时
  • 考虑为您的问题制作一个stackblitz 示例,以便问题更加明显。
  • 考虑在您的输入angular.io/guide/… 中创建一个setter 或拦截更改angular.io/guide/…

标签: angular


【解决方案1】:

子组件app-create-customer在父组件创建时创建并初始化。父级上的this.customer 仅在editCustomer 方法中设置。

ngOnInit 挂钩在创建组件时调用,而不是在您设置某些输入变量时调用。为此,您应该使用 ngOnChanges 钩子。检查哪个输入已更改。根据您的需要,您可以使用*ngIf,仅在选择客户时显示组件,或使用ngOnChanges 对输入的更改采取行动:

解决方案 1:

<app-create-customer *ngIf="customer" [customer]="customer"></app-create-customer>

解决方案 2:

export class CreateCustomerComponent implements OnChanges {
  @Input() isEdit: boolean;
  @Input() customer: ICustomer;

  constructor(private customerDetailsService: CustomerService) { }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.customer && this.customer) {
      console.log('A customer is here', this.customer);
    } 
  }
}

【讨论】:

  • 解决方案 2 有效,但由于某种原因解决方案 1 无效。没关系,因为我不太关心这一点。我可以看到@Michael D 提出的解决方案有效,现在很困惑这是最好的方法
  • @Tom 我错过了你正在使用模态的事实,在这种情况下,迈克尔 D 的答案更好
  • 感谢 Poul 让我知道
【解决方案2】:

@Input() 值始终为未定义

您在 ngOnInit 中得到 undefined,因为在初始化组件时,您实际上并没有传入 customer 值。

在您的子组件中试试这个:

import { Component, OnInit, Input } from '@angular/core';
import { CustomerService } from '../../services/customer/customer.service';
import { ICustomer } from '../../models/customer.model';

@Component({
  selector: 'app-create-customer',
  templateUrl: './create-customer.component.html',
  styleUrls: ['./create-customer.component.scss']
})
export class CreateCustomerComponent implements OnInit {
  @Input() isEdit: boolean;
  @Input() customer: ICustomer;
  constructor(private customerDetailsService: CustomerService) { }

  ngOnInit(): void {
    setTimeout(() => {
       console.log('Inside create customer component' + this.customer);
    });        
  }


}

【讨论】:

    【解决方案3】:

    我不确定当父模板在父 openModal() 函数中初始化时,为什么要显式调用父模板中的模态选择器。您可以从父模板中移除组件选择器,并在父控制器中设置模态输入变量。

    试试下面的

    父组件控制器

    export class CustomerComponent implements OnInit {
      ...
    
      public editCustomer(custDetails: ICustomer): void {
        this.isEdit = true;
        this.customer = custDetails;     // <-- initialize the `customer` variable before opening modal
        this.openModal();
        console.log(this.customer);
      }
    
      private openModal(): void {
        this.modal = this.modalService.open(CreateCustomerComponent,
          { size: 'fullscreen', centered: true, backdrop: 'static' }
        );
    
        this.modal.componentInstance.customer = this.customer;    // <-- send input here
        this.modal.componentInstance.isEdit = this.isEdit;
      }
    }
    

    父组件模板

    <div class="container">
      <div class="row">
      ...
      </div>
    </div>
    
    <!-- don't invoke modal component here -->
    

    更新:从模态向父级发送值

    Angular EventEmitters 是 RxJS Subject 的扩展。因此,您可以使用componentInstance 直接在父级中订阅它们,类似于@Input 变量。您可以使用 Modal close() 函数从父组件中关闭模态。

    试试下面的

    父组件控制器

    import { Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    
    export class AppComponent implements OnDestroy {
      completed$ = new Subject<any>();      // <-- use to close subscriptions
      modal: any;
    
      constructor(private _NgbModal: NgbModal) { }
    
      openModal() {
        this.modal = this._NgbModal.open(NgModalComponent, {
          size: 'fullscreen', centered: true, backdrop: 'static' 
        });
    
        this.modal.componentInstance.customer = customer;
    
        this.modal.componentInstance.someValue.pipe(   // <-- subscribe to an `@Output()` from modal component
          takeUntil(this.completed$)                   // <-- close subscription when `completed$` emits
        ).subscribe(
          value => {
            if (value === 'close') {
              this.closeModal();
            }
          }
        );
      }
    
      closeModal() {                                 // <-- close modal
        this.modal.close('Close click');
        this.completed$.next();                      // <-- close active emitter subscriptions
      }
    
      ngOnDestroy() {
        this.completed$.complete();                  // <-- close impending subscriptions 
      }
    }
    

    模态组件控制器

    import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
    
    export class CreateCustomerComponent implements OnInit {
      @Input() isEdit: boolean;
      @Input() customer: ICustomer;
    
      @Output() someValue = new EventEmitter<any>();
    
      constructor(private customerDetailsService: CustomerService) { }
    
      ngOnInit(): void {
        console.log('Inside create customer component' + this.customer);
      }
    
      emitValue() {
        this.someValue.emit('close');
      }
    }
    

    【讨论】:

    • 你的意思是,我没有将 childselector 粘贴在组件标记中。否则如何检索传递给子组件的值
    • openModal()函数所示,我们可以使用ModalcomponentInstance将数据传递给modal。
    • 我的意思是你将如何检索子组件中的组件实例?
    • 您不必修改当前子组件代码。使用父级的componentInstance@Input() 变量赋值。使用上面的代码,您孩子中的console.log() 应该已经打印了consumer(它可能会打印[Object object])而不是undefined
    • 谢谢迈克尔,我现在可以在子组件中看到对象对象。但是 jsut 混淆了哪种方法最适合。 @Poul Kruijt 提到的方法也可以正常工作。
    猜你喜欢
    • 2017-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多