Как правильно отслеживать изменение данных сервиса и отрисовывать их в компоненте?

Имеется компонент, который работает с сервисом TableService. При срабатывании клика на кнопке и вызове функции actionHandler() должен срабатывать соответствующий метод сервиса, который изменяет состояние programmers, который хранится в сервисе. Как правильно в компоненте Ангуляра отслеживается изменение состояния данных? Если заметите, что я где то тут что то неправильно написал - попрошу поправить, так как я совершенно новенький в ангуляре. Учу Ангуляр по документации

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss'],
  providers: [
    TableService
  ]
})

export class TableComponent {
  displayedColumns: Array<String> = ['id', 'Firstname', 'Lastname', 'Middlename', 'Position', 'Date Of Birth', 'Active', 'operations'];
  dataSource: Array<Programmer> = this.tableService.getData();

  constructor(private bottomSheet: MatBottomSheet, private tableService: TableService) {
  }

  actionHandler(action: string, element?: Programmer): any {
    if (action === ActionType.EDIT) {
      console.log(element);
      const options = new MatBottomSheetConfig();
      options.data = {element};
      return this.bottomSheet.open(PopupComponent, options);
    }
    if (action === ActionType.ADD) {
      return this.bottomSheet.open(PopupComponent);
    }
    return;
  }

  getData(): void {
    this.dataSource = this.tableService.getData();
  }

  remove(id: number): void {
    this.tableService.remove(id);
    this.getData();
  }
}

TableService

@Injectable({
  providedIn: 'root'
})
export class TableService {
  private programmers: Programmer[] = [
    {
      id: 1,
      firstName: 'Павел',
      lastName: 'Петров',
      middleName: 'Валериевич',
      position: Position.JUNIOR,
      dateOfBirth: 'Thu Mar 10 2022 00:00:00 GMT+0600 (Омск, стандартное время)',
      active: true
    },
    {
      id: 12,
      firstName: 'Павел',
      lastName: 'Петров',
      middleName: 'Иванович',
      position: Position.JUNIOR,
      dateOfBirth: 'Thu Jul 16 1998 00:00:00 GMT+0700 (Омск, летнее время)',
      active: true
    },

    {
      id: 13,
      firstName: 'Павел',
      lastName: 'Петров',
      middleName: 'Петрович',
      position: Position.SENIOR,
      dateOfBirth: '11/01/2001',
      active: true
    }
  ]

  constructor() {
  }

  getData(): Array<Programmer> {
    return this.programmers;
  }

  remove(id: number) {
    this.programmers = this.programmers.filter(programmer => programmer.id !== id);
  }

  add(programmer: Programmer) {
    this.programmers.push(programmer);
    console.log('added new programmer', this.programmers);
  }

  edit(id: Programmer) {
    this.programmers.map((el, index) => {
      if (el.id === id.id) {
        this.programmers[index] = id;
        return;
      }
      return;
    });
  }
}

Ответы (1 шт):

Автор решения: Sergey Glazirin

Наиболее распространенный способ - это использовать observable из rxjs и пайп async.

data.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class DataService {
  // приватное поле с данными, чтобы нельзя было изменить извне, хранит сами данные
  private _arrayList = [];
  // приватный объект Subject, он осуществляет подписку, также заблокирован от внешних изменений
  private arrayList$ = new BehaviorSubject<number[]>([]);

  // метод, который возвращает подписку как `Observable`. Знак доллара на конце показывает, что это Observable или Subject и на него можно подписаться.
  public getArrayList$() {
    return this.arrayList$.asObservable();
  }

  public addItem() {
    // изменяем данные, которые у нас хранятся в сервисе
    this._arrayList.push(Math.random());
    // через метод next() передаем обновленное значение в Subject
    this.arrayList$.next(this._arrayList);
  }

  public removeItem() {
    // аналогично методу добавления
    this._arrayList.shift();
    this.arrayList$.next(this._arrayList);
  }
}

app.component.ts

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [DataService],
})
export class AppComponent implements OnInit {
  // объявляем поле с типом Subject
  array$: Observable<number[]>;

  constructor(private readonly dataService: DataService) {}

  ngOnInit() {
    // задаем значение из сервиса, передаем его как Observable.
    this.array$ = this.dataService.getArrayList$();
  }

  // тут просто вызываем методы сервиса
  addItem() {
    this.dataService.addItem();
  }

  removeItem() {
    this.dataService.removeItem();
  }
}

app.html

<button (click)="addItem()">+</button>
<button (click)="removeItem()">-</button>

<!-- Добавлем данные и через async-pipe подписываемся-->
<!-- Для удобства, полученные данные помещаем в переменную шаблона - array-->
<ng-container *ngIf="array$ | async; let array">
  <!-- Тут уже используем данные из сервиса, обращаясь к array -->
  <div *ngFor="let item of array">{{ item }}</div>
</ng-container>

В данном случае получаем следующие приемущества:

  1. Данные изолированны от внешнего воздействия напрямую
  2. Подписка и отписка на данные происходит сама, за счет async-пайпа

Подробнее можно почитать тут, и тут, и тут

Пример на stackblitz

→ Ссылка