From 0eacadc3f4f4387dabfaca0e4630ad94b2466a4c Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Wed, 2 Oct 2024 17:12:20 +0200
Subject: [PATCH] OZG-6731 multiple stateResources

---
 ...anisationseinheit-container.component.html |  10 +-
 ...rganisationseinheit-container.component.ts |   7 +-
 .../organisationseinheit-form.component.html  |  67 ++++++------
 ...rganisationseinheit-form.component.spec.ts |  48 +++------
 .../organisationseinheit-form.component.ts    |  12 +--
 .../organisationseinheit.formservice.ts       |  30 +++---
 .../user/keycloak.resource.service.spec.ts    |  31 +++---
 .../src/lib/user/keycloak.resource.service.ts | 102 ++++++++++--------
 8 files changed, 143 insertions(+), 164 deletions(-)

diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html
index 3e7af75f49..973b18c636 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html
@@ -13,10 +13,12 @@
     label="Neue Organisationseinheit anlegen"
   >
   </admin-secondary-button>
-  <admin-spinner
-    data-test-id="organisationseinheit-spinner"
-    *ngIf="deleteInProgress$ | async"
-  ></admin-spinner>
+  <ng-container *ngIf="deleteStateResource$ | async as deleteStateResource">
+    <admin-spinner
+      data-test-id="organisationseinheit-spinner"
+      *ngIf="deleteStateResource.loading"
+    ></admin-spinner>
+  </ng-container>
 
   <admin-organisationseinheit-list
     [organisationseinheitItems]="organisationseinheitItems"
diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts
index b85e0c8447..7ae9fa331f 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts
@@ -1,4 +1,4 @@
-import { StateResource } from '@alfa-client/tech-shared';
+import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared';
 import { Component, OnInit, ViewChild } from '@angular/core';
 import { Observable, of } from 'rxjs';
 import { Organisationseinheit } from '../../user/user.model';
@@ -11,7 +11,8 @@ import { OrganisationseinheitFormComponent } from './organisationseinheit-form/o
 })
 export class OrganisationseinheitContainerComponent implements OnInit {
   organisationseinheitItems$: Observable<StateResource<Organisationseinheit[]>>;
-  deleteInProgress$: Observable<boolean> = of(false);
+
+  deleteStateResource$: Observable<StateResource<string>> = of(createEmptyStateResource<string>());
 
   @ViewChild(OrganisationseinheitFormComponent)
   private form!: OrganisationseinheitFormComponent;
@@ -31,6 +32,6 @@ export class OrganisationseinheitContainerComponent implements OnInit {
   }
 
   public delete(organisationseinheit: Organisationseinheit): void {
-    this.deleteInProgress$ = this.organisationseinheitService.delete(organisationseinheit.id);
+    this.deleteStateResource$ = this.organisationseinheitService.delete(organisationseinheit.id);
   }
 }
diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.html
index 3bd257438d..12eae70179 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.html
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.html
@@ -1,34 +1,35 @@
-<ng-container *ngIf="submitInProgress$ | async"></ng-container>
-<dialog #OrganisationseinheitDialog data-test-id="organisationseinheit-dialog" class="bg-gray-50">
-  <button
-    (click)="OrganisationseinheitDialog.close()"
-    data-test-id="organisationseinheit-close-button"
-    class="absolute right-3 top-1 text-2xl text-black hover:font-bold active:text-black/80"
-  >
-    &#x2715;
-  </button>
-  <form [formGroup]="formService.form" class="m-5 grid grid-cols-1 gap-5">
-    <h1 class="text-2xl" data-test-id="organisationseinheit-form-header">
-      {{ label }}
-    </h1>
-    <text-field
-      label="Name"
-      data-test-id="organisationseinheit-name"
-      [formControlName]="OrganisationseinheitFormService.ORGANISATIONSEINHEIT_NAME_FIELD"
-    ></text-field>
-    <text-field
-      label="OrganisationseinheitID"
-      data-test-id="organisationseinheit-id"
-      [formControlName]="OrganisationseinheitFormService.ORGANISATIONSEINHEIT_IDS_FIELD"
-    ></text-field>
-
-    <ods-button-with-spinner
-      data-test-id="organisationseinheit-save-button"
-      class="justify-self-end"
-      (clickEmitter)="submit()"
-      [stateResource]="organisationseinheitItems"
-      text="Speichern"
+<ng-container *ngIf="submitStateResource$ | async as submitStateResource">
+  <dialog #OrganisationseinheitDialog data-test-id="organisationseinheit-dialog" class="bg-gray-50">
+    <button
+      (click)="OrganisationseinheitDialog.close()"
+      data-test-id="organisationseinheit-close-button"
+      class="absolute right-3 top-1 text-2xl text-black hover:font-bold active:text-black/80"
     >
-    </ods-button-with-spinner>
-  </form>
-</dialog>
+      &#x2715;
+    </button>
+    <form [formGroup]="formService.form" class="m-5 grid grid-cols-1 gap-5">
+      <h1 class="text-2xl" data-test-id="organisationseinheit-form-header">
+        {{ label }}
+      </h1>
+      <text-field
+        label="Name"
+        data-test-id="organisationseinheit-name"
+        [formControlName]="OrganisationseinheitFormService.ORGANISATIONSEINHEIT_NAME_FIELD"
+      ></text-field>
+      <text-field
+        label="OrganisationseinheitID"
+        data-test-id="organisationseinheit-id"
+        [formControlName]="OrganisationseinheitFormService.ORGANISATIONSEINHEIT_IDS_FIELD"
+      ></text-field>
+
+      <ods-button-with-spinner
+        data-test-id="organisationseinheit-save-button"
+        class="justify-self-end"
+        (clickEmitter)="submit()"
+        [stateResource]="submitStateResource"
+        text="Speichern"
+      >
+      </ods-button-with-spinner>
+    </form>
+  </dialog>
+</ng-container>
diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts
index 55b944fec8..b45d0b36c7 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts
@@ -8,12 +8,7 @@ import {
 } from '@alfa-client/test-utils';
 import { DebugElement } from '@angular/core';
 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
-import {
-  AbstractControl,
-  FormsModule,
-  ReactiveFormsModule,
-  UntypedFormGroup,
-} from '@angular/forms';
+import { AbstractControl, FormsModule, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
 import { ButtonWithSpinnerComponent } from '@ods/component';
 import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
 import { MockComponent, ngMocks } from 'ng-mocks';
@@ -30,9 +25,7 @@ describe('OrganisationseinheitFormComponent', () => {
   let fixture: ComponentFixture<OrganisationseinheitFormComponent>;
   let form: UntypedFormGroup;
 
-  const organisationseinheitService: Mock<OrganisationseinheitService> = mock(
-    OrganisationseinheitService,
-  );
+  const organisationseinheitService: Mock<OrganisationseinheitService> = mock(OrganisationseinheitService);
 
   const saveButtonSelector: string = getDataTestIdOf('organisationseinheit-save-button');
   const closeButtonSelector: string = getDataTestIdOf('organisationseinheit-close-button');
@@ -64,34 +57,20 @@ describe('OrganisationseinheitFormComponent', () => {
 
   describe('form element', () => {
     const fields: string[][] = [
-      [
-        OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD,
-        'Name',
-        'organisationseinheit-name',
-      ],
-      [
-        OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD,
-        'OrganisationseinheitID',
-        'organisationseinheit-id',
-      ],
+      [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD, 'Name', 'organisationseinheit-name'],
+      [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD, 'OrganisationseinheitID', 'organisationseinheit-id'],
     ];
 
-    it.each(fields)(
-      'should have label for field "%s" with name "%s"',
-      (fieldName: string, text: string, inputId: string) => {
-        const textFieldElement = getElementFromFixture(fixture, getDataTestIdOf(inputId));
-        expect(textFieldElement.getAttribute('label')).toBe(text);
-      },
-    );
+    it.each(fields)('should have label for field "%s" with name "%s"', (fieldName: string, text: string, inputId: string) => {
+      const textFieldElement = getElementFromFixture(fixture, getDataTestIdOf(inputId));
+      expect(textFieldElement.getAttribute('label')).toBe(text);
+    });
 
     it.each(fields)('should bind form for text-field "%s"', (fieldName, text, dataTestId) => {
       const fieldValue: string = `some text-field ${text}`;
       const formControl: AbstractControl = form.get(fieldName);
 
-      const textFieldComponent: DebugElement = getDebugElementFromFixtureByCss(
-        fixture,
-        getDataTestIdOf(dataTestId),
-      );
+      const textFieldComponent: DebugElement = getDebugElementFromFixtureByCss(fixture, getDataTestIdOf(dataTestId));
       ngMocks.change(textFieldComponent, fieldValue);
       expect(formControl.value).toBe(fieldValue);
     });
@@ -101,10 +80,7 @@ describe('OrganisationseinheitFormComponent', () => {
     let saveButtonComponent: ButtonWithSpinnerComponent;
 
     beforeEach(() => {
-      saveButtonComponent = getDebugElementFromFixtureByCss(
-        fixture,
-        saveButtonSelector,
-      ).componentInstance;
+      saveButtonComponent = getDebugElementFromFixtureByCss(fixture, saveButtonSelector).componentInstance;
     });
 
     it('should call submit on click', () => {
@@ -141,7 +117,7 @@ describe('OrganisationseinheitFormComponent', () => {
       component.formService.submit = () => throwError(() => new Error('some error'));
 
       component.submit();
-      component.submitInProgress$.subscribe({
+      component.submitStateResource$.subscribe({
         error: () => {},
       });
       tick();
@@ -153,7 +129,7 @@ describe('OrganisationseinheitFormComponent', () => {
       component.formService.submit = () => of(false);
 
       component.submit();
-      component.submitInProgress$.subscribe();
+      component.submitStateResource$.subscribe();
       tick();
 
       expect(component.handleProgressChange).toHaveBeenCalled();
diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.ts
index 9796922d5e..13c41df783 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.ts
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit-form.component.ts
@@ -1,4 +1,4 @@
-import { StateResource, createStateResource } from '@alfa-client/tech-shared';
+import { StateResource, createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared';
 import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
 import { Observable, of, tap } from 'rxjs';
 import { Organisationseinheit } from '../../../user/user.model';
@@ -21,7 +21,7 @@ export class OrganisationseinheitFormComponent implements AfterViewInit {
   @ViewChild('OrganisationseinheitDialog') private dialogRef: ElementRef<HTMLDialogElement>;
   dialog: HTMLDialogElement;
 
-  submitInProgress$: Observable<boolean> = of(false);
+  submitStateResource$: Observable<StateResource<Organisationseinheit>> = of(createEmptyStateResource<Organisationseinheit>());
 
   label: string;
 
@@ -32,13 +32,13 @@ export class OrganisationseinheitFormComponent implements AfterViewInit {
   }
 
   public submit() {
-    this.submitInProgress$ = this.formService
+    this.submitStateResource$ = this.formService
       .submit()
-      .pipe(tap((progress: boolean) => this.handleProgressChange(progress)));
+      .pipe(tap((stateResource: StateResource<Organisationseinheit>) => this.handleProgressChange(stateResource)));
   }
 
-  handleProgressChange(progress: boolean): void {
-    if (!progress) {
+  handleProgressChange(stateResource: StateResource<Organisationseinheit>): void {
+    if (!stateResource.loading) {
       this.completeIfNoErrors();
     }
   }
diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts
index 3309b9a813..127b99ff73 100644
--- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts
+++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts
@@ -1,12 +1,8 @@
-import { createStateResource, isNotNil, StateResource } from '@alfa-client/tech-shared';
+import { createEmptyStateResource, createStateResource, isNotNil, StateResource } from '@alfa-client/tech-shared';
 import { Injectable, Input } from '@angular/core';
 import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
-import { catchError, Observable, of } from 'rxjs';
-import {
-  Organisationseinheit,
-  OrganisationseinheitError,
-  OrganisationseinheitErrorType,
-} from '../../../user/user.model';
+import { catchError, Observable, of, tap } from 'rxjs';
+import { Organisationseinheit, OrganisationseinheitError, OrganisationseinheitErrorType } from '../../../user/user.model';
 import { getOrganisationseinheitErrorMessage } from '../../../user/user.util';
 import { OrganisationseinheitService } from '../../organisationseinheitService';
 
@@ -32,31 +28,32 @@ export class OrganisationseinheitFormservice {
     });
   }
 
-  public submit(): Observable<boolean> {
+  public submit(): Observable<StateResource<Organisationseinheit>> {
     if (this.validate()) {
       return this.callService().pipe(
+        tap((state) => console.log(state)),
         catchError((error: OrganisationseinheitError) => {
           this.handleError(error);
-          return of(false);
+          return of(createEmptyStateResource<Organisationseinheit>());
         }),
       );
     } else {
-      return of(false);
+      return of(createEmptyStateResource<Organisationseinheit>());
     }
   }
 
-  callService(): Observable<boolean> {
+  callService(): Observable<StateResource<Organisationseinheit>> {
     return this.isPatch() ? this.save() : this.create();
   }
 
-  create(): Observable<boolean> {
+  create(): Observable<StateResource<Organisationseinheit>> {
     return this.organisationsEinheitService.create({
       name: this.getName(),
       organisationseinheitIds: this.getOrganisationseinheitIds(),
     });
   }
 
-  save(): Observable<boolean> {
+  save(): Observable<StateResource<Organisationseinheit>> {
     return this.organisationsEinheitService.save({
       ...this.source,
       name: this.getName(),
@@ -79,9 +76,7 @@ export class OrganisationseinheitFormservice {
   }
 
   private getName(): string {
-    return this.getStringFromPotentiallyEmptyField(
-      OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD,
-    );
+    return this.getStringFromPotentiallyEmptyField(OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD);
   }
 
   private getOrganisationseinheitIds(): string[] {
@@ -126,8 +121,7 @@ export class OrganisationseinheitFormservice {
     this.source = organisationseinheit;
     this.form.patchValue({
       [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD]: organisationseinheit.name,
-      [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD]:
-        organisationseinheit.organisationseinheitIds.join(', '),
+      [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD]: organisationseinheit.organisationseinheitIds.join(', '),
     });
   }
 
diff --git a/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.spec.ts b/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.spec.ts
index ee89610514..540130a154 100644
--- a/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.spec.ts
+++ b/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.spec.ts
@@ -1,10 +1,7 @@
 import { fakeAsync, tick } from '@angular/core/testing';
 import faker from '@faker-js/faker';
 import { cold } from 'jest-marbles';
-import {
-  StateResource,
-  createEmptyStateResource,
-} from 'libs/tech-shared/src/lib/resource/resource.util';
+import { StateResource, createEmptyStateResource } from 'libs/tech-shared/src/lib/resource/resource.util';
 import { createDummy } from 'libs/tech-shared/test/dummy';
 import { singleCold } from 'libs/tech-shared/test/marbles';
 import { Observable, of } from 'rxjs';
@@ -30,7 +27,7 @@ describe('KeycloakResourceService', () => {
 
     it('should return stateResource as observable', (done) => {
       service.get().subscribe((stateResource) => {
-        expect(stateResource).toBe(service.stateResource.value);
+        expect(stateResource).toBe(service.listStateResource.value);
         done();
       });
     });
@@ -68,7 +65,7 @@ describe('KeycloakResourceService', () => {
       service.loadResource();
       tick();
 
-      expect(service.stateResource.value.resource).toEqual(dummyItems);
+      expect(service.listStateResource.value.resource).toEqual(dummyItems);
     }));
   });
 
@@ -132,7 +129,7 @@ describe('KeycloakResourceService', () => {
     it('should set loading', () => {
       service.handleLoading(dummyAction);
 
-      expect(service.stateResource.value.loading).toBe(true);
+      expect(service.listStateResource.value.loading).toBe(true);
     });
 
     it('should call refreshAfterFirstEmit', () => {
@@ -154,12 +151,12 @@ describe('KeycloakResourceService', () => {
 
   describe('refreshAfterFirstEmit', () => {
     it('should call refresh after first emit', fakeAsync(() => {
-      service.refresh = jest.fn();
+      service.refreshList = jest.fn();
 
       service.refreshAfterFirstEmit(dummyAction).subscribe();
       tick();
 
-      expect(service.refresh).toHaveBeenCalled();
+      expect(service.refreshList).toHaveBeenCalled();
     }));
   });
 
@@ -175,36 +172,36 @@ describe('KeycloakResourceService', () => {
     it('should set loading in state to true without parameter', () => {
       service.setLoading();
 
-      expect(service.stateResource.value.loading).toEqual(true);
+      expect(service.listStateResource.value.loading).toEqual(true);
     });
 
     it('should set loading in state to false for parameter false', () => {
       service.setLoading(false);
 
-      expect(service.stateResource.value.loading).toEqual(false);
+      expect(service.listStateResource.value.loading).toEqual(false);
     });
   });
 
   describe('refresh', () => {
     it('should set reload in state to true', () => {
-      service.refresh();
+      service.refreshList();
 
-      expect(service.stateResource.value.reload).toBe(true);
+      expect(service.listStateResource.value.reload).toBe(true);
     });
 
     it('should clear resource in state', () => {
-      service.refresh();
+      service.refreshList();
 
-      expect(service.stateResource.value.resource).toBe(null);
+      expect(service.listStateResource.value.resource).toBe(null);
     });
   });
 
   describe('select resource', () => {
     it('should return state resource', () => {
       const stateResource = createEmptyStateResource<unknown[]>();
-      service.stateResource.next(stateResource);
+      service.listStateResource.next(stateResource);
 
-      const resource$: Observable<StateResource<unknown[]>> = service.selectResource();
+      const resource$: Observable<StateResource<unknown[]>> = service.selectListResource();
 
       expect(resource$).toBeObservable(singleCold(stateResource));
     });
diff --git a/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.ts b/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.ts
index 914d547f4a..ed608c73a9 100644
--- a/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.ts
+++ b/alfa-client/libs/admin/settings/src/lib/user/keycloak.resource.service.ts
@@ -1,20 +1,14 @@
-import {
-  createEmptyStateResource,
-  createStateResource,
-  doIfLoadingRequired,
-  StateResource,
-} from '@alfa-client/tech-shared';
-import { BehaviorSubject, first, map, Observable, startWith, tap } from 'rxjs';
+import { createEmptyStateResource, createStateResource, doIfLoadingRequired, StateResource } from '@alfa-client/tech-shared';
+import { BehaviorSubject, first, Observable, startWith, switchMap, tap } from 'rxjs';
 
 export abstract class KeycloakResourceService<T> {
-  readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject(
-    createEmptyStateResource(),
-  );
+  readonly listStateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject(createEmptyStateResource());
+  readonly createStateResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject(createEmptyStateResource());
+  readonly deleteStateResource: BehaviorSubject<StateResource<string>> = new BehaviorSubject(createEmptyStateResource());
+  readonly saveStateResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject(createEmptyStateResource());
 
   public get() {
-    return this.stateResource
-      .asObservable()
-      .pipe(tap((stateResource) => this.handleChanges(stateResource)));
+    return this.listStateResource.asObservable().pipe(tap((stateResource) => this.handleChanges(stateResource)));
   }
 
   handleChanges(stateResource: StateResource<T[]>) {
@@ -22,70 +16,84 @@ export abstract class KeycloakResourceService<T> {
   }
 
   loadResource(): void {
-    this.setLoading();
+    this.setListLoading(this.listStateResource);
     this.getItemsFromKeycloak()
       .pipe(first())
       .subscribe((items) => this.updateResource(items));
   }
 
+  setListLoading(stateResource: BehaviorSubject<StateResource<unknown>>, loading: boolean = true): void {
+    stateResource.next({
+      ...stateResource.value,
+      loading,
+    });
+  }
+
   abstract getItemsFromKeycloak(): Observable<T[]>;
 
   private updateResource(items: T[]): void {
-    this.stateResource.next(createStateResource(items));
+    this.listStateResource.next(createStateResource(items));
+  }
+
+  public create(item: Partial<T>): Observable<StateResource<T>> {
+    this.createStateResource.next(createEmptyStateResource(true));
+
+    return this.createInKeycloak(item).pipe(
+      tap((item) => this.updateCreateStateResource(item)),
+      tap(() => this.refreshList()),
+      switchMap(() => this.createStateResource.asObservable()),
+      startWith(this.createStateResource.value),
+    );
   }
 
-  public create(item: Partial<T>): Observable<boolean> {
-    return this.handleLoading(this.createInKeycloak(item));
+  updateCreateStateResource(item: T): void {
+    this.createStateResource.next(createStateResource(item));
   }
 
   abstract createInKeycloak(item: Partial<T>): Observable<T>;
 
-  public save(item: T): Observable<boolean> {
-    return this.handleLoading(this.saveInKeycloak(item));
-  }
+  public save(item: T): Observable<StateResource<T>> {
+    this.saveStateResource.next(createEmptyStateResource(true));
 
-  abstract saveInKeycloak(item: T): Observable<void>;
+    return this.saveInKeycloak(item).pipe(
+      tap(() => this.updateSaveStateResource(item)),
+      tap(() => this.refreshList()),
+      switchMap(() => this.saveStateResource.asObservable()),
+      startWith(this.saveStateResource.value),
+    );
+  }
 
-  public delete(id: string): Observable<boolean> {
-    return this.handleLoading(this.deleteInKeycloak(id));
+  updateSaveStateResource(item: T): void {
+    this.saveStateResource.next(createStateResource(item));
   }
 
-  abstract deleteInKeycloak(id: string): Observable<void>;
+  abstract saveInKeycloak(item: T): Observable<void>;
 
-  handleLoading(action: Observable<unknown>): Observable<boolean> {
-    this.setLoading();
-    return this.progress(this.refreshAfterFirstEmit(action));
-  }
+  public delete(id: string): Observable<StateResource<string>> {
+    this.deleteStateResource.next(createEmptyStateResource(true));
 
-  refreshAfterFirstEmit(action: Observable<unknown>): Observable<unknown> {
-    return action.pipe(
-      first(),
-      tap(() => this.refresh()),
+    return this.deleteInKeycloak(id).pipe(
+      tap(() => this.updateDeleteStateResource(id)),
+      tap(() => this.refreshList()),
+      switchMap(() => this.deleteStateResource.asObservable()),
+      startWith(this.deleteStateResource.value),
     );
   }
 
-  progress(action: Observable<unknown>): Observable<boolean> {
-    return action.pipe(
-      map(() => false),
-      startWith(true),
-    );
+  updateDeleteStateResource(id: string): void {
+    this.deleteStateResource.next(createStateResource(id));
   }
 
-  setLoading(loading: boolean = true): void {
-    this.stateResource.next({
-      ...this.stateResource.value,
-      loading,
-    });
-  }
+  abstract deleteInKeycloak(id: string): Observable<void>;
 
-  refresh(): void {
-    this.stateResource.next({
+  refreshList(): void {
+    this.listStateResource.next({
       ...createEmptyStateResource(),
       reload: true,
     });
   }
 
-  public selectResource(): Observable<StateResource<T[]>> {
-    return this.stateResource.asObservable();
+  public selectListResource(): Observable<StateResource<T[]>> {
+    return this.listStateResource.asObservable();
   }
 }
-- 
GitLab