Home  >  Q&A  >  body text

Angular - How to update a child component that takes an input parameter and renders it correctly the first time

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粉463418483P粉463418483211 days ago409

reply all(2)I'll reply

  • P粉658954914

    P粉6589549142024-03-22 14:12:38

    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

    reply
    0
  • P粉022723606

    P粉0227236062024-03-22 13:08:51

    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

    < /a>

    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
    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 required< /里>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
    • Typical limitations of using reactive code, such as only mutating via streams, needing to avoid unsubscriptions (if not using | async), etc.

    reply
    0
  • Cancelreply