BehaviorSubject: почему он работает без использования next

У меня есть приложение Angular, и я создал класс для своего списка контактов, который состоит из:

export interface Contact {
    id: number;
    name: string;
    address: string;
}

export class ContactList {
    private contactList: Contact[];
    private contactNumber: number;

public getContactList(): Contact[] {
        return this.contactList;
    }

// Methods to add, modify and remove a contact
}

Затем у меня есть служба, которая создает экземпляр этого класса, создает BehaviorSubject для совместного использования с другими компонентами и имеет несколько общедоступных методов.

export class ContactListService {

  public contactList: ContactList;
  private contactList$: BehaviorSubject<Contact[]>;

  constructor() {
    this.contactList = new ContactList(FAKE_CONTACTS);
    this.contactList$ = new BehaviorSubject<Contact[]>(this.contactList.getContactList());
  }

  public getContactList(): BehaviorSubject<Contact[]> {
    return this.contactList$;
  }

  public deleteContact(contactId: number): void {
    this.contactList.deleteContact(contactId);
  }

  public addContact(newName: string, newAddress: string): void {
    this.contactList.addContact(newName, newAddress);
  }

  public modifyContact(contactId: number, newName?: string, newAddress?: string): void {
    this.contactList.modifyContact(contactId, newName, newAddress);
  }
}

Затем в компоненте я подписываюсь на BehaviorSubject и влияю на значение свойства моего компонента.

ngOnInit() {
    this.contactListSubscription = this.contactListService.getContactList().subscribe((newContactList) => {
      this.contactList = newContactList;
    });
  }

Так что он работает (т.е. все обновляется везде, когда я выполняю действие через службу). Но я не понимаю, что содержимое подписки (т.е. this.contactList = newContactList) выполняется только один раз в подписке, а не каждый раз, когда происходит действие. Даже если я изменю содержимое с помощью метода contactListService. И даже если я откажусь от подписки, например, через 2 секунды после подписки (например, с помощью setTimeout), контент всегда будет актуальным после отказа от подписки ...

Сначала я даже не понимал, почему он работает в сервисах, не делая contactList$.next(this.contactList.getContactList()) после каждого действия, изменяющего объект.

Кажется, я передал какие-то ссылки вместо содержимого класса? Кажется, я не понимаю, как работает BehaviorSubject!


person Mahé    schedule 19.11.2019    source источник


Ответы (1)


JavaScript всегда передает ссылки на объекты.

Таким образом, массив contactList в компоненте является ссылкой на тот же уникальный массив, который хранится в вашем свойстве ContactList.contactList.

Итак, вы изменяете массив, компонент имеет ссылку на этот измененный массив, а Angular обнаруживает изменения в массиве и применяет изменения к DOM.

Он бы больше не работал, если бы методы в ContactList заменили массив другим (измененной копией), т.е. сделали, например,

this.contactList = [this.contactList..., newContact]

В этом случае компонент будет продолжать ссылаться на предыдущий немодифицированный массив.

Чтобы проиллюстрировать кодом:

service.contactList = ['John', 'Mary'];
component.contactList = service.contactList; // the exact, same array

service.contactList.push('Helen'); // add an element to the unique array
console.log(component.contactList); // logs John, Mary, Helen: it's the same, unique array

service.contactList = ['Jack']; // now the service references a second, different array
console.log(component.contactList); // Still logs John, Mary, Helen
person JB Nizet    schedule 19.11.2019
comment
Итак, если я правильно понимаю, здесь BehaviorSubject вообще ничего не делает! Я только прохожу через них ссылки. Итак, как мне добиться правильного способа сделать это (т.е. передать значение service contactList, а не ссылку на него)? Это проблема, потому что в моем примере только службе разрешено изменять содержимое этого экземпляра contactList? - person Mahé; 19.11.2019
comment
Вы возвращаете копию массива из getContactList (), чтобы убедиться, что внутренний массив остается неизменяемым за пределами класса ContactList: return [...this.contactList];. Обратите внимание, что это все равно сделает каждый контакт внутри массива изменяемым отовсюду, если они изменяемы: массив содержит ссылки на объекты. - person JB Nizet; 19.11.2019
comment
Ok! Я уже сталкивался с этой проблемой в проекте Java, я понимаю, что поведение здесь такое же. Итак, чтобы улучшить свой код, я должен передать (как вы написали) копию массива и вызвать next в contactList $ после каждого изменения в моем сервисе, чтобы уведомить каждый компонент, который подписывается, что объект был обновлен! Спасибо - person Mahé; 19.11.2019