Skip to content

Commit 227d946

Browse files
committed
add AsyncState<T,E> pattern & migrate all facades from boolean flags
1 parent 711ebbd commit 227d946

25 files changed

Lines changed: 355 additions & 205 deletions

projects/social_platform/src/app/api/export-file/facades/export-file-info.service.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { saveFile } from "@utils/helpers/export-file";
66
import { ProgramDetailMainUIInfoService } from "../../program/facades/detail/ui/program-detail-main-ui-info.service";
77
import { Subject, takeUntil } from "rxjs";
88
import { LoggerService } from "@corelib";
9+
import { AsyncState, failure, initial, loading, success } from "../../../domain/shared/async-state";
910

1011
@Injectable()
1112
export class ExportFileInfoService {
@@ -17,64 +18,61 @@ export class ExportFileInfoService {
1718

1819
private readonly program = this.programDetailMainUIInfoService.program;
1920

20-
readonly loadingExportProjects = signal<boolean>(false);
21-
readonly loadingExportSubmittedProjects = signal<boolean>(false);
22-
readonly loadingExportRates = signal<boolean>(false);
23-
readonly loadingExportCalculations = signal<boolean>(false);
21+
readonly loadingExports$ = signal<AsyncState<void>>(initial());
2422

2523
destroy(): void {
2624
this.destroy$.next();
2725
this.destroy$.complete();
2826
}
2927

3028
downloadProjects(): void {
31-
this.loadingExportProjects.set(true);
29+
this.loadingExports$.set(loading());
3230

3331
this.exportFileService
3432
.exportAllProjects(this.program()!.id)
3533
.pipe(takeUntil(this.destroy$))
3634
.subscribe({
3735
next: blob => {
3836
saveFile(blob, "all", this.program()?.name);
39-
this.loadingExportProjects.set(false);
37+
this.loadingExports$.set(success(undefined));
4038
},
4139
error: err => {
4240
this.loggerService.error(err);
43-
this.loadingExportProjects.set(false);
41+
this.loadingExports$.set(failure(`export_file_${err}`));
4442
},
4543
});
4644
}
4745

4846
downloadSubmittedProjects(): void {
49-
this.loadingExportSubmittedProjects.set(true);
47+
this.loadingExports$.set(loading());
5048

5149
this.exportFileService
5250
.exportSubmittedProjects(this.program()!.id)
5351
.pipe(takeUntil(this.destroy$))
5452
.subscribe({
5553
next: blob => {
5654
saveFile(blob, "submitted", this.program()?.name);
57-
this.loadingExportSubmittedProjects.set(false);
55+
this.loadingExports$.set(success(undefined));
5856
},
59-
error: () => {
60-
this.loadingExportSubmittedProjects.set(false);
57+
error: err => {
58+
this.loadingExports$.set(failure(`export_file_${err}`));
6159
},
6260
});
6361
}
6462

6563
downloadRates(): void {
66-
this.loadingExportRates.set(true);
64+
this.loadingExports$.set(loading());
6765

6866
this.exportFileService
6967
.exportProgramRates(this.program()!.id)
7068
.pipe(takeUntil(this.destroy$))
7169
.subscribe({
7270
next: blob => {
7371
saveFile(blob, "rates", this.program()?.name);
74-
this.loadingExportRates.set(false);
72+
this.loadingExports$.set(success(undefined));
7573
},
76-
error: () => {
77-
this.loadingExportRates.set(false);
74+
error: err => {
75+
this.loadingExports$.set(failure(`export_file_${err}`));
7876
},
7977
});
8078
}

projects/social_platform/src/app/api/feed/facades/feed-info.service.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { FeedUIInfoService } from "./ui/feed-ui-info.service";
2020
import { FetchFeedUseCase } from "../use-cases/fetch-feed.use-case";
2121
import { ReadFeedNewsUseCase } from "../use-cases/read-feed-news.use-case";
2222
import { ToggleFeedLikeUseCase } from "../use-cases/toggle-feed-like.use-case";
23+
import { isSuccess, loading, success } from "../../../domain/shared/async-state";
2324

2425
const DEFAULT_FEED_TYPES: FeedItemType[] = ["vacancy", "project", "news"];
2526
const FILTER_SPLIT_SYMBOL = "|";
@@ -67,6 +68,9 @@ export class FeedInfoService {
6768
this.feedUIInfoService.totalItemsCount.set(0);
6869
this.feedUIInfoService.feedPage.set(0);
6970

71+
const prev = this.feedUIInfoService.feedItems();
72+
this.feedUIInfoService.feedItems$.set(loading(prev));
73+
7074
return this.onFetch(
7175
0,
7276
this.feedUIInfoService.perFetchTake(),
@@ -113,11 +117,12 @@ export class FeedInfoService {
113117

114118
if (uniqueNewItems.length > 0) {
115119
this.feedUIInfoService.feedPage.update(page => page + uniqueNewItems.length);
116-
this.feedItems.update(items => {
117-
const next = [...items, ...uniqueNewItems];
118-
queueMicrotask(() => this.observeFeedItems());
119-
return next;
120-
});
120+
this.feedUIInfoService.feedItems$.update(state =>
121+
isSuccess(state)
122+
? success([...state.data, ...uniqueNewItems])
123+
: success(uniqueNewItems)
124+
);
125+
queueMicrotask(() => this.observeFeedItems());
121126
}
122127
})
123128
);
Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,52 @@
11
/** @format */
22

3-
import { Injectable, signal } from "@angular/core";
3+
import { computed, Injectable, signal } from "@angular/core";
44
import { ApiPagination } from "projects/social_platform/src/app/domain/other/api-pagination.model";
55
import { FeedItem } from "projects/social_platform/src/app/domain/feed/feed-item.model";
6+
import {
7+
AsyncState,
8+
initial,
9+
isLoading,
10+
isSuccess,
11+
success,
12+
} from "projects/social_platform/src/app/domain/shared/async-state";
613

714
@Injectable()
815
export class FeedUIInfoService {
9-
readonly feedItems = signal<FeedItem[]>([]);
16+
readonly feedItems$ = signal<AsyncState<FeedItem[]>>(initial());
17+
18+
readonly feedItems = computed(() => {
19+
const state = this.feedItems$();
20+
if (isSuccess(state)) return state.data;
21+
if (isLoading(state)) return state.previous ?? [];
22+
return [];
23+
});
1024

1125
readonly totalItemsCount = signal(0);
1226
readonly feedPage = signal(0);
1327
readonly perFetchTake = signal(20);
1428

1529
applyInitializationFeedNewsEvent(feed: ApiPagination<FeedItem>): void {
16-
this.feedItems.set(feed.results);
30+
this.feedItems$.set(success(feed.results));
1731
this.totalItemsCount.set(feed.count);
1832
this.feedPage.set(feed.results.length);
1933
}
2034

2135
applyFeedFilters(feed: FeedItem[]): void {
22-
this.feedItems.set(feed);
36+
this.feedItems$.set(success(feed));
2337
this.feedPage.set(feed.length);
2438
}
2539

2640
applyLikeNews(itemIdx: number): void {
27-
this.feedItems.update((items: any) => {
41+
this.feedItems$.update(state => {
42+
if (!isSuccess(state)) return state;
43+
44+
const items = state.data;
2845
const item = items[itemIdx];
2946

30-
const updated = {
47+
if (item.typeModel !== "news") return state;
48+
49+
const updated: FeedItem = {
3150
...item,
3251
content: {
3352
...item.content,
@@ -38,7 +57,7 @@ export class FeedUIInfoService {
3857
},
3958
};
4059

41-
return items.map((it: any, i: number) => (i === itemIdx ? updated : it));
60+
return success(items.map((it, i) => (i === itemIdx ? updated : it)));
4261
});
4362
}
4463
}

projects/social_platform/src/app/api/member/facades/members-info.service.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ProjectsDetailUIInfoService } from "../../project/facades/detail/ui/pro
2828
import { LoggerService } from "projects/core/src/lib/services/logger/logger.service";
2929
import { AuthRepository } from "../../../infrastructure/repository/auth/auth.repository";
3030
import { GetMembersUseCase } from "../use-case/get-members.use-case";
31+
import { isSuccess, loading, success } from "../../../domain/shared/async-state";
3132

3233
@Injectable()
3334
export class MembersInfoService {
@@ -44,9 +45,10 @@ export class MembersInfoService {
4445
private readonly searchParams = signal<Record<string, string>>({}); // Signal для параметров поиска
4546

4647
private readonly membersTake = this.membersUIInfoService.membersTake; // Количество участников на странице
47-
private readonly members = this.membersUIInfoService.members; // Массив участников для отображения
4848
private readonly profileId = this.projectsDetailUIInfoService.profileId;
4949

50+
private readonly members = this.membersUIInfoService.members; // Массив участников для отображения
51+
5052
private readonly searchForm = this.membersUIInfoService.searchForm;
5153
private readonly filterForm = this.membersUIInfoService.filterForm;
5254

@@ -131,11 +133,15 @@ export class MembersInfoService {
131133
if (params["age"] && /\d+,\d+/.test(params["age"])) fetchParams["age"] = params["age"];
132134

133135
this.searchParams.set(fetchParams);
136+
137+
const prev = this.membersUIInfoService.members();
138+
this.membersUIInfoService.members$.set(loading(prev));
139+
134140
return this.onFetch(0, 20, fetchParams);
135141
})
136142
)
137143
.subscribe(members => {
138-
this.membersUIInfoService.applyQueryParams(members);
144+
this.membersUIInfoService.members$.set(success(members.results));
139145
});
140146
}
141147

@@ -167,9 +173,17 @@ export class MembersInfoService {
167173

168174
if (diff > 0) {
169175
// Загружаем следующую порцию участников
170-
return this.onFetch(this.members().length, this.membersTake(), this.searchParams()).pipe(
176+
return this.onFetch(
177+
this.membersUIInfoService.members().length,
178+
this.membersTake(),
179+
this.searchParams()
180+
).pipe(
171181
tap(membersChunk => {
172-
this.membersUIInfoService.applyMembersChunk(membersChunk);
182+
this.membersUIInfoService.members$.update(state =>
183+
isSuccess(state)
184+
? success([...state.data, ...membersChunk.results])
185+
: success(membersChunk.results)
186+
);
173187
})
174188
);
175189
}
Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
/** @format */
22

3-
import { inject, Injectable, signal } from "@angular/core";
3+
import { computed, inject, Injectable, signal } from "@angular/core";
44
import { FormBuilder, Validators } from "@angular/forms";
55
import { ApiPagination } from "projects/skills/src/models/api-pagination.model";
66
import { User } from "projects/social_platform/src/app/domain/auth/user.model";
7+
import {
8+
AsyncState,
9+
initial,
10+
isLoading,
11+
isSuccess,
12+
success,
13+
} from "projects/social_platform/src/app/domain/shared/async-state";
714

815
@Injectable()
916
export class MembersUIInfoService {
@@ -13,7 +20,14 @@ export class MembersUIInfoService {
1320
readonly membersTake = signal<number>(20); // Количество участников на странице
1421
private readonly membersPage = signal<number>(1); // Текущая страница для пагинации
1522

16-
readonly members = signal<User[]>([]); // Массив участников для отображения
23+
readonly members$ = signal<AsyncState<User[]>>(initial()); // Массив участников для отображения
24+
25+
readonly members = computed(() => {
26+
const state = this.members$();
27+
if (isSuccess(state)) return state.data;
28+
if (isLoading(state)) return state.previous ?? [];
29+
return [];
30+
});
1731

1832
// Форма поиска с обязательным полем для ввода имени
1933
readonly searchForm = this.fb.group({
@@ -30,18 +44,6 @@ export class MembersUIInfoService {
3044

3145
applyMembersPagination(members: ApiPagination<User>) {
3246
this.membersTotalCount.set(members.count);
33-
this.members.set(members.results);
34-
}
35-
36-
applyQueryParams(members: ApiPagination<User>): void {
37-
this.members.set(members.results || []);
38-
this.membersTotalCount.set(members.count);
39-
this.membersPage.set(1);
40-
}
41-
42-
applyMembersChunk(membersChunk: ApiPagination<User>): void {
43-
this.membersPage.update(page => page + 1);
44-
this.members.update(list => [...list, ...(membersChunk.results || [])]);
45-
this.membersTotalCount.set(membersChunk.count);
47+
this.members$.set(success(members.results));
4648
}
4749
}
Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
/** @format */
22

3-
import { Injectable, signal } from "@angular/core";
3+
import { computed, Injectable, signal } from "@angular/core";
44
import { FeedNews } from "../../domain/project/project-news.model";
55
import { ApiPagination } from "../../domain/other/api-pagination.model";
6+
import {
7+
AsyncState,
8+
initial,
9+
isLoading,
10+
isSuccess,
11+
success,
12+
} from "../../domain/shared/async-state";
613

714
@Injectable({ providedIn: "root" })
815
export class NewsInfoService {
9-
readonly news = signal<FeedNews[]>([]); // Массив новостей
16+
readonly news$ = signal<AsyncState<FeedNews[]>>(initial()); // Массив новостей
17+
18+
readonly news = computed(() => {
19+
const state = this.news$();
20+
if (isSuccess(state)) return state.data;
21+
if (isLoading(state)) return state.previous ?? [];
22+
return [];
23+
});
1024

1125
applySetNews(
1226
news:
@@ -16,36 +30,48 @@ export class NewsInfoService {
1630
count: number;
1731
}
1832
): void {
19-
this.news.set(news.results);
33+
this.news$.set(success(news.results));
2034
}
2135

22-
applyAddNews(newsRes: any): void {
23-
this.news.update(list => [newsRes, ...list]);
36+
applyAddNews(newsRes: FeedNews): void {
37+
this.news$.update(state =>
38+
isSuccess(state) ? success([newsRes, ...state.data]) : success([newsRes])
39+
);
2440
}
2541

2642
applyUpdateNews(results: FeedNews[]): void {
27-
this.news.update(news => [...news, ...results]);
43+
this.news$.update(state =>
44+
isSuccess(state) ? success([...state.data, ...results]) : success(results)
45+
);
2846
}
2947

3048
applyDeleteNews(newsId: number): void {
31-
this.news.update(news => news.filter(n => n.id !== newsId));
49+
this.news$.update(state =>
50+
isSuccess(state) ? success(state.data.filter(n => n.id !== newsId)) : state
51+
);
3252
}
3353

3454
applyEditNews(resNews: FeedNews): void {
35-
this.news.update(news => news.map(n => (n.id === resNews.id ? resNews : n)));
55+
this.news$.update(state =>
56+
isSuccess(state) ? success(state.data.map(n => (n.id === resNews.id ? resNews : n))) : state
57+
);
3658
}
3759

3860
applyLikeNews(newsId: number): void {
39-
this.news.update(list =>
40-
list.map(n =>
41-
n.id === newsId
42-
? {
43-
...n,
44-
isUserLiked: !n.isUserLiked,
45-
likesCount: n.isUserLiked ? n.likesCount - 1 : n.likesCount + 1,
46-
}
47-
: n
48-
)
61+
this.news$.update(list =>
62+
isSuccess(list)
63+
? success(
64+
list.data.map(n =>
65+
n.id === newsId
66+
? {
67+
...n,
68+
isUserLiked: !n.isUserLiked,
69+
likesCount: n.isUserLiked ? n.likesCount - 1 : n.likesCount + 1,
70+
}
71+
: n
72+
)
73+
)
74+
: list
4975
);
5076
}
5177
}

0 commit comments

Comments
 (0)