Angular - How to update a child component that takes an input parameter and renders it correctly the first time
P粉463418483
P粉463418483 2024-03-21 21:06:55
0
2
466

My educational experience is as follows:

<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>

And the following components

<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>

Has the following logic for displaying data obtained from parameters:

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;
    }
}

I use custom events to handle updates

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

  constructor() {}

  ngOnInit(): void {}

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

Then I have the following html for manipulating the data:

<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>

However, the updated data in the field [value]="loadedEducation.school" (updatedValue)="loadedEducation.school = $event" will not be bound to the child component, so after refreshing and getting No data from the database will be displayed before.

What possibilities can I try to implement?

I tried to implement ngOnChanges but without success.

P粉463418483
P粉463418483

reply all(2)
P粉658954914

The loadedEducations list does not change when you change the properties of the items in the list. Try refreshing the list (this.loadedEducations = returnedEducations) or use state management

in your project
P粉022723606

The root cause of the problem is that @Input() cannot detect changes inside objects and arrays because they are both reference types. Your education property is an object, so changes made in the parent component that directly change the property (e.g. education.school = 'newValue') will not trigger the child component's property# Any changes to ##@Input() education

There are several ways to solve this problem, each with its advantages and disadvantages:


Pass only the properties you need as primitives

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
  }
}

advantage:

    Simple and intuitive to use, works “normally”
  • No need for additional boilerplate to send changes
  • to child components
shortcoming:

Additional boilerplate is required to
    receive
  • changes to child components. As the number of @Input grows, it becomes unwieldy You lose the semantic coupling between parent and child components, they are actually bound by a shared interface (i.e. the
  • Education
  • interface) Does not scale well if properties are also reference types, in which case these properties also need to be unpacked and passed as primitives
Rebuild object in parent when changed

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
  }
}

Deep Clone

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
  }
}

advantage:

Retain the use of the
    Education
  • interface to maintain the semantic coupling between parent components and child components Encourage the use of
  • Immutable objects
  • It is generally a good practice for objects No additional boilerplate is required to
  • receive changes to
  • subcomponents.
shortcoming:

Additional boilerplate is needed to
    send
  • changes to child components, i.e. create redundant updateEducation() functions in the parent component
Pass reactive elements into your subcomponent, such as
BehaviorSubject

, and subscribe to changes directly

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 }}

advantage:

Full control over event sending/subscription. This is good for any other side effects you wish to trigger
  • Can be easily extended to use many components, such as putting the
  • educationSubject
  • into a service and injecting the same service into any component that needs it Also advocates the use of
  • immutable objects
  • No additional boilerplate requiredReceive changes to
  • subcomponents
shortcoming:

  • Additional boilerplate is needed to send changes to child components, i.e. create redundant updateEducation() functions in the parent component
  • Typical limitations of using reactive code, such as only mutating via streams, needing to avoid unsubscriptions (if not using | async), etc.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template