javascript - Hur man undviker "ExpressionChangedAfterItHasBeenCheckedError" eller "Cannot Read Property of Undefined" när man använder information från "@ViewChild"?


Jag skulle vilja förstå några frågor om @ViewChild och hur det fungerar. Jag använder Angular 8, och av någon anledning fick jag några fel, såsom "ExpressionChangedAfterItHasBeenCheckedError" eller "Cannot Read Property ... of Undefined"

Jag försökte använda {static: false} på min @ViewChild, men jag fick samma fel. Så, hur skulle jag kunna lösa detta problem och hur fungerar det?

Jag förstår att barnkomponenter körs efter att föräldern har visats. Jag använde några metoder, såsom ngAfterViewInit och AfterViewChecked, för att vänta på barninformationen, men jag fick samma fel. Missar jag något?

Här är min kod nedan:

app-parent.component.ts

@Component({ selector: 'app-parent', templateUrl: './app-parent.component.html' })
export class AppParentComponent implements OnInit, AfterViewChecked {

  @ViewChild('backbtn', {static: false}) backReference : ChildBackComponent;
  previousurl : string = null;

  /*...codes...*/
  ngAfterViewChecked() {
    this.previousurl = this.backReference.previousUrl;
  }
  /*...codes...*/

app-parent.component.html

<!-- HTML codes -->
<app-child-back-button #backbtn >Button</app-child-back-button>
<app-another-child-element *ngIf="currentUrl" [url]="previousurl" ></app-another-child-element>

och

child-back-button.component.ts

@Component({ selector: "app-child-back-button", templateUrl: "./child-back-button.component.html" })
export class ChildBackComponent implements OnInit {
  public previousUrl: string = "/";

  ngOnInit() {
    this.previousUrl = "/previousurl";
  }

child-back-button.component.html

<a routerLink="{{ previousUrl }}"><ng-content></ng-content></a>

Redigera

Tack vare @tony-ngo löser jag detta problem.

Bara ändra min kod till

app-parent.component.ts

@Component({ selector: 'app-parent', templateUrl: './app-parent.component.html' })
export class AppParentComponent implements OnInit, AfterViewInit {

  @ViewChild('backbtn', {static: false}) backReference : ChildBackComponent;
  previousurl : string = null;

  /*...codes...*/
  ngAfterViewInit() {
    setTimeout(() => {
      this.previousurl = this.backReference.previousUrl;
    }, 1000);
  }

  /*...codes...*/

Enligt min åsikt verkar det inte vara en elegant sak att göra, men det löser mitt problem och fungerar bra. Kanske behöver du vänta tills alla komponenter blir tillgängliga innan du hämtar information från viewchild.

Den andra lösningen verkar vara mer komplex, jag måste hantera och upptäcka ändringar, bara för att lösa ett problem som endast uppstår i utvecklingsläge. Så, den första lösningen är bra för mig. Tack!

Svar #1

Baserat på den här artikeln finns det några sätt du kan fixa det

Sidnotering: eftersom du försöker få senaste uppdaterade värdet från ditt view child behöver du uppdatera din kod

@ViewChild('backbtn', {static: true}) backReference : ChildBackComponent;

Använd setTimeout ungefär så här

  ngAfterViewChecked() {
    setTimeout(() => {
        this.previousurl = this.backReference.previousUrl;
    }, 1000); // set timeout for 1 second you can change 2000 or 3000
  }

Flytta din logik till ngOnInit

ngOnInit() {
    this.previousurl = this.backReference.previousUrl;
  }

Använd ChangeDection på det här sättet

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

Eller å andra sidan, du behöver se till att Angular plockar upp förändringar. Det föredragna sättet att göra det är att använda markForCheck.