Tenho um serviço que notifica que um conjunto de categorias foi preenchido (por meio de uma chamada a uma API no bootstrap do aplicativo). Assim que essas categorias estiverem prontas, preciso iniciar uma chamada à API para cada categoria contida no conjunto para encontrar os detalhes dos dados de cada categoria, que serão exibidos em diferentes seções da mesma página.
A notificação pode ocorrer de duas formas diferentes, ao final do primeiro carregamento do aplicativo (lançado apenas uma vez) ou toda vez que um usuário navegando pelo aplicativo chega na página e o array de categorias já foi preenchido.
Este é o código do componente.
export class CategoriesComponent implements OnInit, OnDestroy {
tagsCategories: TagCategory[]|null = null;
dateRangeTags: {start: Date, end: Date};
component_destroy = new Subject<void>();
constructor(...)
{
const d = new Date();
this.dateRangeTags = {start: this.commonFunc.getOneMonthAgo(d), end: d};
}
ngOnInit() {
console.log('init', this.tagsCategories);
this.loader.loadingOn();
if (this.sharedDataService.getTagCategories().length > 0) {
//categories have already been loaded
this.sharedData.notifyTagsCategoriesReady();
}
this.sharedData.categoriesAvailable$
.pipe(
tap(() => {
const categories = this.sharedData.getTagCategories();
for (const category of categories) {
category.date_range_request = {...this.dateRangeTags};
}
this.tagsCategories = categories;
console.log('tap side effect', this.tagsCategories);
this.loader.loadingOff();
}),
filter((v,i) => this.tagsCategories !== null && this.tagsCategories.length > 0),
switchMap(
() => from(this.tagsCategories!).pipe(
mergeMap((category: TagCategory) =>
this.fetchTagsCategories(category.date_range_request!, category)
.pipe(
tap({
next: (categories: TagCategory[]) => {
if (categories.length > 0) {
const cat = categories[0];
if (cat.id === category.id) {
category.tags_plots = cat.tags_plots;
}
}
},
error: (error) => {
category.spinner = false;
},
complete: () => {
category.spinner = false;
}
}),
catchError((error) => throwError(() => error))
)
)
)
)
)
.subscribe({
next: (val) => {
console.log('final next', val);
},
error: (error: string) => {
this.errorHandler.displayErrorDialog(error);
},
complete: () => {}
})
}
}
Este é o assunto no serviço e a função que chamo para notificar a disponibilidade das categorias nos dois cenários diferentes.
export class SharedDataService {
categories: TagCategory[] = [];
private _categoriesAvailable = new Subject<boolean>();
categoriesAvailable$ = this._categoriesAvailable.asObservable();
//this is called when the categories are fetched from the server
setTagCategories(tagCategories: TagCategory[]) {
this.categories = tagCategories;
console.log('Set tag categories');
this._categoriesAvailable.next(true);
}
//this is called when the user visit the categories page and the array is already loaded
notifyTagsCategoriesReady() {
console.log('notifyTagsCategoriesReady');
this._categoriesAvailable.next(true);
}
getTagCategories() {
return this.categories;
}
}
Na visualização de componentes, inseri uma proteção para não exibir as várias seções se o array de categorias ainda não tiver sido inicializado ou estiver vazio.
<h1 class="nom">{{ 'categories.title' | translate }}</h1>
@if (tagsCategories && tagsCategories.length > 0) {
@for (tagCategory of tagsCategories; track tagCategory.id) {
<!-- build each section -->
}
Quando o aplicativo é completamente recarregado na página de categoria e, em seguida, o aplicativo é inicializado, tudo funciona como deveria, mas se você navegar para outras páginas e retornar a essa página, as várias seções não são mostradas, mesmo que o console.log no operador tap indique que o array está preenchido corretamente. Além disso, toda vez que você revisita a página, as solicitações se multiplicam como se a mesma assinatura fosse adicionada a cada vez; se o array contiver três elementos, toda vez que eu revisito a página, 3 solicitações são adicionadas. Também tentei inserir um takeUntil notificando a destruição do componente, mas nesse caso a assinatura só funciona na primeira vez e nunca é recriada depois, mesmo que o console.log dentro da função ngOnInit seja impresso.
É claro que estou esquecendo de muitas coisas e talvez existam implementações melhores para conseguir o que preciso fazer, mas ficaria grato se alguém pudesse me dizer por que o Angular parece ignorar a mudança de estado do array ao não mostrar as várias seções após o primeiro carregamento e, acima de tudo, por que as solicitações se multiplicam toda vez que visito a página.
Atualizar:
Certifique-se de inicializar a assinatura antes de emitir
next
o assunto.O motivo da multiplicação de chamadas é que a assinatura não foi cancelada. Então, podemos adicionar o código abaixo:
Então, devemos converter o
Subject
no serviço para umBehaviorSubject
porque o código dentro do componente não será acionado, a menos que o assunto tenha seunext
método chamado, o que não acontece durante a navegação.