typescript - שירות API של Angular עם BehaviorSubject לא מרענן את הנתונים של הרכיבים בזמן אמת


יצרתי שירות CRUD שמבצע קריאות ל-API קלאסי. אני משתמש ב-BehaviorSubject כדי לשתף נתונים בכל הרכיבים של האפליקציה שלי. הבעיה היא שכאשר אני מבצע פעולות CRUD, הנתונים לא מתעדכנים בכל הרכיבים בזמן אמת. הנתונים יתעדכנו אם אני navigates לרכיב אחר.

אני לא מבין למה זה לא עובד בזמן אמת ואני לא מצליח למצוא מאיפה הטעות שלי.

אם מישהו יכול לעזור לי זה יהיה מאוד נחמד :)

ניסיתי לקרוא לפונקציה get Candidates() שמעדכנת את מאגר הנתונים עם הפונקציה next() בכל ngOnInit() של הקומפוננטים שלי אך ההתנהגות היא אותה אחת

מאגר GitHub: https://github.com/jbty/signalr-majority-vote/tree/master/signalR-majority-vote/ClientApp

שירות api של Angular :

export class ApiCandidateService {
  public candidatesList: Observable<Candidate[]>;
  private baseUrl: string;
  private _candidatesList: BehaviorSubject<Candidate[]>;
  private dataStore: {
    candidatesList: Candidate[];
  };
  private httpOptions: { headers: HttpHeaders } = {
    headers: new HttpHeaders({
      "Content-Types": "applications/json"
    })
  };

  public constructor(private http: HttpClient) {
    this.baseUrl = "api/Candidates";
    this.dataStore = { candidatesList: [] };
    this._candidatesList = <BehaviorSubject<Candidate[]>>new BehaviorSubject([]);
    this.candidatesList = this._candidatesList.asObservable();
  }

  public getCandidates() {
    this.http.get<Candidate[]>(this.baseUrl, this.httpOptions).subscribe(
      (data: Candidate[]) => {
        this.dataStore.candidatesList = data;
        this._candidatesList.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public getCandidate(id: number | string) {
    this.http.get<Candidate>(`${this.baseUrl}/${id}`, this.httpOptions).subscribe(
      (data: Candidate)  => {
        let notFound = true;

        this.dataStore.candidatesList.forEach((item, index) => {
          if (item.candidateId == data.candidateId) {
            this.dataStore.candidatesList[index] = data;
            notFound = false;
          }
        });

        if (notFound) {
          this.dataStore.candidatesList.push(data);
        }

        this._candidatesList.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public createCandidate(newCandidate: Candidate) {
    this.http.post<Candidate>(`${this.baseUrl}`, newCandidate, this.httpOptions).subscribe(
      (data: Candidate) => {
        this.dataStore.candidatesList.push(data);
        this._candidatesList.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public updateCandidate(id: number | string, updatedCandidate: Candidate) {
    this.http.put<Candidate>(`${this.baseUrl}/${id}`, updatedCandidate, this.httpOptions).subscribe(
      () => {
        this.dataStore.candidatesList.forEach((candidate, i) => {
          if (candidate.candidateId == updatedCandidate.candidateId) {
            this.dataStore.candidatesList[i] = updatedCandidate;
          }
        });

        this._candidatesList.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public deleteCandidate(id: number | string) {
    this.http.delete<Candidate>(`${this.baseUrl}/${id}`, this.httpOptions).subscribe(
      response => {
        this.dataStore.candidatesList.forEach((candidate, i) => {
          if (candidate.candidateId == Number(id)) {
            this.dataStore.candidatesList.splice(i, 1);
          }
        });

        this._candidatesList.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error("An error occurred:", error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(`Backend returned code ${error.status}, ` + `body was: ${error.error}`);
    }
    // return an observable with a user-facing error message
    return throwError("Something bad happened; please try again later.");
  }
}

רכיב דוגמה

export class DeleteCandidatesComponent implements OnInit {
  public candidate: Observable<Candidate>;
  public candidateId: string;

  constructor(
    public nav: NavigationService,
    private route: ActivatedRoute,
    private candidateService: ApiCandidateService
  ) {
    this.candidateId = this.route.snapshot.params.id;
  }

  public ngOnInit(): void {
    this.candidate = this.candidateService.candidatesList.pipe(
      map(candidate => candidate.find(item => item.candidateId == Number(this.candidateId)))
    );

    this.candidateService.getCandidate(this.candidateId);
  }

  public onDefinitivlyDeleteCandidate(id: string) {
    this.candidateService.deleteCandidate(id);
    this.nav.outletsNav('classement', 'manage/candidates')
  }
}

הדעה שלי

<header>
  <h2>
    êtes vous sûr de vouloir supprimer définitvement
    <strong>{{ (candidate | async)?.firstName }} {{ (candidate | async)?.lastName }}</strong> ?
  </h2>
</header>
<aside class="submit">
  <button mat-button color="primary" (click)="onDefinitivlyDeleteCandidate(candidateId)">
    Oui
  </button>
  <button mat-button color="warn" (click)="nav.outletsNav('classement', 'manage/candidates')">
    annuler
  </button>
</aside>

תשובה #1

עדכון

export class ApiCandidateService {

  private baseUrl: string;
  private dataStore: {
    candidatesList: Candidate[];
  };
  private _store = new BehaviourSubject<Candidate []>(dataStore);
  public state$: this._store.asObservable();
  public candidatesList$

  private httpOptions: { headers: HttpHeaders } = {
    headers: new HttpHeaders({
      "Content-Types": "applications/json"
    })
  };

  public constructor(private http: HttpClient) {
    this.baseUrl = "api/Candidates";

    this.candidatesList$ = this.state$.pipe(map(state => 
                            state.candidatesList), 
                            distinctUntilChanged());

  }

  public getCandidates() {
    this.http.get<Candidate[]>(this.baseUrl, this.httpOptions).subscribe(
      (data: Candidate[]) => {
        this.dataStore.candidatesList = data;
        this._store.next(Object.assign({}, 
           this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public getCandidate(id: number | string) {
    this.http.get<Candidate>(`${this.baseUrl}/${id}`, this.httpOptions).subscribe(
      (data: Candidate)  => {
        let notFound = true;

        this.dataStore.candidatesList.forEach((item, index) => {
          if (item.candidateId == data.candidateId) {
            this.dataStore.candidatesList[index] = data;
            notFound = false;
          }
        });

        if (notFound) {
          this.dataStore.candidatesList.push(data);
        }

        this._store.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public createCandidate(newCandidate: Candidate) {
    this.http.post<Candidate>(`${this.baseUrl}`, newCandidate, this.httpOptions).subscribe(
      (data: Candidate) => {
        this.dataStore.candidatesList.push(data);
        this._store.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public updateCandidate(id: number | string, updatedCandidate: Candidate) {
    this.http.put<Candidate>(`${this.baseUrl}/${id}`, updatedCandidate, this.httpOptions).subscribe(
      () => {
        this.dataStore.candidatesList.forEach((candidate, i) => {
          if (candidate.candidateId == updatedCandidate.candidateId) {
            this.dataStore.candidatesList[i] = updatedCandidate;
          }
        });

        this._store.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  public deleteCandidate(id: number | string) {
    this.http.delete<Candidate>(`${this.baseUrl}/${id}`, this.httpOptions).subscribe(
      response => {
        this.dataStore.candidatesList.forEach((candidate, i) => {
          if (candidate.candidateId == Number(id)) {
            this.dataStore.candidatesList.splice(i, 1);
          }
        });

        this._store.next(Object.assign({}, this.dataStore).candidatesList);
      },
      error => {
        this.handleError(error);
      }
    );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error("An error occurred:", error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(`Backend returned code ${error.status}, ` + `body was: ${error.error}`);
    }
    // return an observable with a user-facing error message
    return throwError("Something bad happened; please try again later.");
  }
}

ברכיב התצוגה

 export class DeleteCandidatesComponent implements OnInit {
  public candidate: Observable<Candidate>;
  public candidateId: string;

  constructor(
    public nav: NavigationService,
    private route: ActivatedRoute,
    private candidateService: ApiCandidateService
  ) {
    this.candidateId = this.route.snapshot.params.id;

**this.candidate$ = this.candidateService.candidatesList$.pipe(
      map(candidate => candidate.find(item => item.candidateId == Number(this.candidateId)))
    );**
  }

  public ngOnInit(): void {


    this.candidateService.getCandidate(this.candidateId);
  }

  public onDefinitivlyDeleteCandidate(id: string) {
    this.candidateService.deleteCandidate(id);
    this.nav.outletsNav('classement', 'manage/candidates')
  }
}

בתבנית

{{ (candidate$ | async)?.firstName }} {{ (candidate$ | 
             async)?.lastName }} 

תשובה #2

אתה מעדכן את המאפיין שלך ב-ngOnInit(), אשר ייקרא רק כאשר תעבור לתצוגה הזו ולכן יעדכן רק כאשר תעבור לרכיב הזה.

אתה יכול ליצור פונקציית get כדי לקבל את הערך הזה ולעדכן אותו בתצוגה.

רכיב :

 export class DeleteCandidatesComponent implements OnInit {
  public candidate: Observable<Candidate>;
  public candidateId: string;

  constructor(
    public nav: NavigationService,
    private route: ActivatedRoute,
    private candidateService: ApiCandidateService
  ) {
    this.candidateId = this.route.snapshot.params.id;
  }

  public ngOnInit(): void {}

  public onDefinitivlyDeleteCandidate(id: string) {
    this.candidateService.deleteCandidate(id);
    this.nav.outletsNav('classement', 'manage/candidates')
  }

  get getCandidates():  Observable<Candidate>{
     this.candidate = this.candidateService.candidatesList.pipe(
      map(candidate => candidate.find(item => item.candidateId == 
       Number(this.candidateId)))
    );
    this.candidateService.getCandidate(this.candidateId);
    return this.candidate;
  }

}

תצוגה :

<header>
  <h2>
    êtes vous sûr de vouloir supprimer définitvement
    <strong>{{ (getCandidates)?.firstName }} {{ (getCandidates)?.lastName }}</strong> ?
  </h2>
</header>
<aside class="submit">
  <button mat-button color="primary" (click)="onDefinitivlyDeleteCandidate(candidateId)">
    Oui
  </button>
  <button mat-button color="warn" (click)="nav.outletsNav('classement', 'manage/candidates')">
    annuler
  </button>
</aside>