Как я могу заполнить статический массив внутри службы из запроса GET?

То, что я пытаюсь сделать (я так думаю), очень просто. Я создаю веб-приложение, и есть несколько элементов select, которые я хочу заполнить теми же данными из удаленного файла JSON (запрос GET). Я создал сервис и хочу заполнить внутри него статический массив и вызвать его из любого компонента, который захочу. Я думаю, что я упускаю что-то очень важное.
Вот код:
fetchData.service.ts

export interface Creditor {
  id: number;
  name: string;
}

@Injectable()
export class FetchDataService {

    creditorsStaticArray: Creditor[] = [];

    getCreditorsFromAPI() {
        this.http.get<Creditor[]>(this.creditorsUrl)
            .subscribe(
              items => {
                this.creditorsStaticArray = items;
              }
            );
      }

    getCreds() {
        return this.creditorsStaticArray;
    }
}

application-details.component.ts (один из компонентов)

export class ApplicationDetailsComponent implements OnInit {

    loadedCreditors: Creditor[] = [];

    constructor(private fetchDataService: FetchDataService) { }

    ngOnInit() {
        this.fetchDataService.getCreditorsFromAPI();
        this.loadedCreditors = this.fetchDataService.getCreds();
    }

}

Мое решение основано на этом ответе

Любая помощь приветствуется!


person kkaragki    schedule 04.06.2019    source источник
comment
Обработайте обратный вызов вместо назначения или попробуйте с наблюдаемыми, вернув this.http.get<Creditor[]>(this.creditorsUrl) и подписавшись вне службы.   -  person FrV    schedule 04.06.2019
comment
@FrV Как я сказал ниже, я реализовал все это с помощью наблюдаемых, но я увидел, что запрос GET вызывается для каждого компонента, который использует этот сервис, и я думаю, что это будет проблемой производительности, потому что я хочу иметь около 10 вкладок с компонент каждый.   -  person kkaragki    schedule 04.06.2019
comment
Я не думаю, что это будет проблемой производительности, если компоненты не отображаются одновременно: каждый раз вызывать службу правильно. Если компоненты отображаются одновременно: создайте родительский компонент и поделитесь результатом наблюдаемого   -  person FrV    schedule 04.06.2019
comment
@FrV Это отличная идея! Я не думал об этом, и у меня уже есть родительский компонент;) Ура   -  person kkaragki    schedule 04.06.2019


Ответы (2)


Ваш статический массив не может выходить за рамки вашего сервиса. Однако вы также не можете иметь static внутри своего class scope.

Поскольку services в Angular по умолчанию равно singletons, ваша целевая цель будет достигнута, если вы сделаете что-то вроде этого:

@Injectable()
export class FetchDataService {

    creditorsStaticArray: Creditor[] = [];

    getCreditorsFromAPI() {
        this.http.get<Creditor[]>(this.creditorsUrl)
            .subscribe(
              items => {
                this.creditorsStaticArray = items;
              }
            );
      }

    getCreds() {
        return this.creditorsStaticArray;
    }
}

Поскольку вы уже ссылаетесь на this.creditorsStaticArray, это сразу сработает. Возможно, вы захотите переименовать его в creditorsCache или что-то подобное и предотвратить прямой доступ к нему из-за пределов службы (сделав его private), поскольку он больше не static. Но помимо соглашений об именах и ограничителей доступа вы достигаете цели, которую ищете.


Теперь я добавлю несколько лучших практик внизу для вашего дальнейшего использования.

Вы подписываетесь внутри своего Сервиса и не управляете подпиской «явно». Это не обязательно плохо, так как HttpClient по умолчанию завершится после первого результата, но, возможно, стоит сделать это более явным, добавив .pipe(first()) или .pipe(take(1)) (first — это псевдоним take(1)). Таким образом, если ваш API или способ извлечения данных изменится, появится явное напоминание о том, что этот Observable примет 1 значение (весь массив) и завершится сам, сохранив результат в переменной в качестве побочного эффекта.

То, что вы, возможно, захотите рассмотреть, это не subscribing внутри вашего Сервиса, а возврат всего Observable вашим компонентам, чтобы он мог пройти и определить момент подписки. Вы все еще можете сохранить состояние своей переменной, поместив ее в

.pipe(
   tap(data => { this.creditorsCache = data })
)

Когда вы или ваш компонент (или ваш HTML с помощью AsyncPipe) подписывается; он сохранит его в своем кеше, и вы сможете автоматически обрабатывать новые входящие результаты.

В приведенном выше примере вы по-прежнему можете полагаться на свой механизм caching, чтобы не поражать свой сервер каждый раз, возвращая кэшированные данные в виде файла Observable. К счастью, RxJS предоставляет множество возможностей для создания Observables, так что это не должно быть слишком сложным!

Краткий пример:


getCreditorsFromAPI(): Observable<Creditor[]> {
  return this.http.get<Creditor[]>(this.creditorsUrl)
            .pipe(
                tap(data => this.creditorsCache = data)
             )
  );
}

getCreds(): Observable<Creditor[]> {
    // You could also use this to invalidate your cache after 10 minutes!
    if(this.creditorsCache.length > 0) {
        return of(this.creditorsCache)
    }

    // Because I set the return type of this function, you will need to return a valid Observable. This makes your code predictable!
    return this.getCreditorsFromAPI() // This will recreate your cache cause of the tap()!
}

В приведенном выше примере вы должны вызывать только service.getCreds() и управлять подпиской в ​​своем компоненте. Он будет кэшироваться для вас автоматически каждый раз, когда вы переназначаете свой наблюдаемый объект на this.service.getCreds().

Пища для размышлений! Я бы не сказал, что есть идеальный способ делать вещи, и определенно есть больше способов, которые ведут к фигуративному Риму; но то, что я только что описал, определенно немного больше Reactive, на что Angular опирается во многих своих внутренних компонентах.

person Bjorn 'Bjeaurn' S    schedule 04.06.2019
comment
Большое спасибо за ответ. Раньше у меня была ошибка (я редактировал свой вопрос), конечно, у меня был «creditorsStaticArray» внутри моего класса. Дело в том, что список, который я хочу заполнить, пуст:/ - person kkaragki; 04.06.2019
comment
Не забывайте, что вызов API также является асинхронным, поэтому вызов getCreds в хуке жизненного цикла OnInit сразу после инициации вызова, вероятно, просто вернет пустой массив. - person Daniel B; 04.06.2019
comment
@DanielB Да, я думаю, это моя проблема. Итак, когда я должен позвонить getCreds ? - person kkaragki; 04.06.2019
comment
Что ж, это часть реактивного программирования, которую я добавил под своим первоначальным базовым ответом. Вы можете полностью работать с Observables в своем компоненте, поэтому управляйте подпиской там (используя asyncPipe или подобное!) и по-прежнему используйте свой кеш. Вы можете легко сгенерировать Observable из своих значений, используя оператор создания of(), и отправить его. Он будет обрабатываться как любой другой Observable, поэтому вы будете работать с реактивными потоками. :-) - person Bjorn 'Bjeaurn' S; 04.06.2019
comment
Я обновил свой ответ примером того, как вы можете полагаться на свой кеш, не задействуя постоянно свой сервер. - person Bjorn 'Bjeaurn' S; 04.06.2019
comment
@Bjorn'Bjearn'S Ваш пример и ваше понимание мне очень помогли, и я попытался внедрить вашу логику в свой код. Но я столкнулся с проблемой, я думаю: я добавил console.log() внутри if(this.creditorsCache.length > 0) и увидел, что когда я вызываю службу из другого компонента, она все еще выполняет запрос GET. Также я попробовал 2 реализации, и я хотел бы сказать мне, какая из них более «правильная». 1. с Observable, 2. без - person kkaragki; 05.06.2019
comment
Я взгляну! - person Bjorn 'Bjeaurn' S; 05.06.2019
comment
Я бы рекомендовал использовать вариант 1 с Observable. asyncPipe управляет subscribe() за вас. Он даже переключается, если ссылка изменяется, поэтому, когда вы хотите перезагрузить свои данные; вы делаете this.creditorsObservable = this.fetchDataService.getCreds(); снова (вы можете обернуть это в функцию), и ваш HTML автоматически отразит изменения. Ручная подписка и управление имеют свои места, но это достаточно просто! Вы бы не согласились? - person Bjorn 'Bjeaurn' S; 05.06.2019
comment
Что касается this.creditorsCache.length > 0, нам нужно будет изучить, как вы предоставляете свою услугу, чтобы убедиться, что это общая служба/одиночка для всех компонентов. Если каждый создает свой собственный кеш, логично, что он будет обращаться к серверу несколько раз, чтобы создать несколько кешей. Хотя мне кажется это отдельный вопрос. :-). - person Bjorn 'Bjeaurn' S; 05.06.2019
comment
@Bjorn'Bjearn'S Большое спасибо, приятель! Вы помогли мне понять то, что беспокоило меня 2 дня! Я буду придерживаться решения Observable, и мне удалось выяснить, почему this.creditorsCache.length > 0 никогда не было true. Потому что она вызывается 2 раза из разных компонентов, но одновременно, поэтому не успела закешировать creditorsCache. Я чувствую себя таким глупым :/ Еще раз спасибо! - person kkaragki; 05.06.2019
comment
Безумно рад помочь! Напишите мне, если вы столкнетесь с подобными или другими проблемами здесь, в Твиттере или где-то еще. Удачи! - person Bjorn 'Bjeaurn' S; 05.06.2019

Верните Observable из вашего сервиса:

@Injectable()
export class FetchDataService {

    creditorsStaticArray: Creditor[] = [];

    getCreditorsFromAPI() {
        return this.http.get<Creditor[]>(this.creditorsUrl);
      }
}

В вашем компоненте:

 loadedCreditors: Creditor[] = [];

ngOnInit() {
    this.fetchDataService.getCreditorsFromAPI()
        .subscribe(res => this.loadedCreditors = res)

}
person Abderrahim Soubai-Elidrisi    schedule 04.06.2019
comment
Спасибо за Ваш ответ. Это именно моя реализация, но с таким подходом я должен вызывать свой fetchDataService каждый раз, когда я хочу заполнить элемент select. creditorsStaticArray нигде не используется :/ - person kkaragki; 04.06.2019
comment
Вы можете хранить полученные данные из API в localStorage и каждый раз использовать их повторно. - person Abderrahim Soubai-Elidrisi; 04.06.2019