Angular - 如何更新采用输入参数并第一次正确渲染它的子组件
P粉463418483
P粉463418483 2024-03-21 21:06:55
0
2
465

我的教育经历如下:

<cdk-virtual-scroll-viewport itemSize="5" class="list-scroll">
         <app-education-item *ngFor="let education of loadedEducations"
          (isSelected)="changeSelected(education)"
          [ngClass]="{ selected: education == loadedEducation }"
          [education]="education"
          (isRemoved)="removeEducation(education)"
        ></app-education-item>
      </cdk-virtual-scroll-viewport>

并且是以下组件

<div [ngClass]="{ 'list-item-container-collapsed' : isCollapsed, 'list-item-container': !isCollapsed, 'unselected': !isActive, 'selected': isActive}" (click)="selectEducation()">
    <div class="top-items-container" style="display: flex;">
     <div class="item-text">
     <span class="txt-header">{{educationHeader}}</span>
     <p class="txt-date"> 
         <span>{{startDate}}</span> - 
         <span>{{endDate}}</span>
     </p>
 </div>
</div>

具有以下逻辑,用于显示从参数中获取的数据:

export class EducationItemComponent implements OnInit {

  @Input()
  education: Education;
  isCollapsed = false;
  isActive = false;
  startDate: string;
  endDate: string;
  educationHeader: string;
  educationDescription: string;

  constructor() { }

  ngOnInit(): void {
    console.log(this.education);
    this.startDate = this.education.startDate != '' ? formatDate(this.education.startDate, 'MMM yyyy', 'en-US')
        : formatDate(new Date(), 'MM YYYY', 'en-US') ;
    this.endDate = this.education.endDate != 'present' ? this.endDate = formatDate(this.education.endDate, 'MMM yyyy', 'en-US')
        : this.education.endDate;
    this.educationHeader = this.education.degree == undefined || this.education.description == undefined ? ''
        : this.education.degree + ' at ' + this.education.school;

    if (!this.education.description.enUS && this.education.description.nlNL) {
      this.educationDescription = this.education.description.nlNL;
    } else if (this.education.description.enUS) {
      this.educationDescription = this.education.description.enUS;
    }
}

我使用自定义事件来处理更新

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

  constructor() {}

  ngOnInit(): void {}

  fieldChanged(changes: SimpleChanges) {
    this.updatedValue.emit(changes);
  }

然后我有以下 html 用于操作数据:

<div class="update-wrap">
        <div class="list-header">Update education</div>
        <div>
          <div class="col-sm-6 input-wrapper">
            <app-input-field
              label="Institution"
              [value]="loadedEducation.school"
              (updatedValue)="loadedEducation.school = $event"
            ></app-input-field>
          </div>
          <div class="col-sm-6 input-wrapper date-picker-input">
            <app-input-field
              label="Degree"
              [value]="loadedEducation.degree"
              (updatedValue)="loadedEducation.degree = $event"
            ></app-input-field>
          </div>
        </div>
</div>

但是,字段 [value]="loadedEducation.school" (updatedValue)="loadedEducation.school = $event" 中的更新数据不会与子组件绑定,因此在刷新并获取之前不会显示任何内容来自数据库的数据。

我可以尝试实现哪些可能性?

我尝试实现 ngOnChanges,但没有成功。

P粉463418483
P粉463418483

全部回复(2)
P粉658954914

当您更改列表中项目的属性时,loadedEducations 列表不会更改。尝试刷新列表(this.loadedEducations = returnedEducations)或在项目中使用状态管理

P粉022723606

问题的根本原因是 @Input() 无法检测到对象和数组内部的更改,因为它们都是 引用类型。您的 education 属性是一个对象,因此在父组件中进行的直接改变属性的更改(例如 education.school = 'newValue' )不会触发子组件的属性 @Input() education 的任何更改

有几种方法可以解决这个问题,每种方法都有其优点和缺点:


仅传递您需要的属性作为基元

parent.component.ts

education: Education = 

parent.component.html





child.component.ts

export class EducationItemComponent implements OnChanges {
  @Input() school: string;
  @Input() degree: string;

  ngOnChanges(changes: SimpleChanges): void {
    // will emit whenever .school or .degree is changed in the parent
  }
}

优点:

  • 使用简单直观,“正常”工作
  • 无需额外的样板即可将更改发送到子组件

缺点:

  • 需要额外的样板来接收对子组件的更改。随着 @Input 数量的增长,变得笨重
  • 您失去了父组件和子组件之间的语义耦合,它们实际上由共享接口(即 Education 接口)绑定
  • 如果属性也是引用类型,则无法很好地扩展,在这种情况下,这些属性也需要解压并作为基元传递

更改时在父级中重建对象

parent.component.ts

education: Education = 

updateEducation(educationProps: Partial): Education {
  this.education = {
    ...this.education, // Note: You may want to 'deep clone' your object depending on how nested it is
    ...educationProps
  }
}

深度克隆

parent.component.html





child.component.ts

export class EducationItemComponent implements OnChanges {
  @Input() education: Education;

  ngOnChanges(changes: SimpleChanges): void {
    // will emit whenever updateEducation() is called in the parent
  }
}

优点:

  • 保留使用 Education 接口,保持父组件和子组件之间的语义耦合
  • 提倡使用不可变对象通常对于对象来说是一个很好的实践
  • 无需额外的样板即可接收子组件的更改。

缺点:

  • 需要额外的样板来发送更改到子组件,即在父组件中创建多余的 updateEducation() 函数

将反应性元素传递到您的子组件中,例如 BehaviorSubject,并直接订阅更改

parent.component.ts

educationSubject: BehaviorSubject = new BehaviorSubject(  )

updateEducation(educationProps: Partial): Education {
  const updatedEducation: Education = {
    ...this.education, // Note: You may want to 'deep clone' your object depending on how nested it is
    ...educationProps
  }
  this.educationSubject.next(updatedEducation}
}

parent.component.html





  
  

child.component.ts

export class EducationItemComponent implements OnChanges {
  @Input() educationSubject: BehaviorSubject;
}

child.component.html


  

{{ education.school }}

优点:

  • 完全控制事件发送/订阅。这对于您希望触发的任何其他副作用都是有益的
  • 可以轻松扩展以使用许多组件,例如将 educationSubject 放入服务中,并将相同的服务注入到任何需要它的组件中
  • 还提倡使用不可变对象
  • 无需额外的样板即可接收子组件的更改

缺点:

  • 需要额外的样板来发送更改到子组件,即在父组件中创建多余的 updateEducation() 函数
  • 使用反应式代码的典型限制,例如仅通过流进行变异、需要避免取消订阅(如果不使用 | async)等
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板