-1

I have observabled data that I got from service:

public events$: Observable<IEvent[]> = of([]);
public filteredEvents$: BehaviorSubject<IEvent[]> = new BehaviorSubject([]);

this.events$ = this.eventsService.get();

When I call the function:

  public checkAll(): void {
    this.events$
      .pipe(
        map((events: IEvent[]) =>
          events.filter(
            (event: IEvent) => (event.checked_export = !event.checked_export)
          )
        )
      )
      .subscribe((events) => this.filteredEvents$.next(events));
  }

It calls again this.events$ = this.eventsService.get(); with request to the server.

Template is:

<ng-container *ngIf="filteredEvents$ | async; else nodata">

I know that each .subscribe((events) calls observer again, but how to modify observable array this.events$ and return to filteredEvents$?

Why I get this error in code:

RangeError: Maximum call stack size exceeded

Component method is:

  public delete(event: IEvent): void {
    this.confirm
      .open({})
      .pipe(
        filter(Boolean),
        concatMap(() => this.eventService.delete(event))
      )
      .subscribe((response) => this.eventService.next(response));
  }

Service is:

  public next(events: IEvent[]): void {
    this.events$.next(events);
  }

  public events(): Observable<any> {
    return this.events$.asObservable();
  }

  public delete(event: IEvent): Observable<any> {
    return this.eventsService
      .delete(event)
      .pipe(
        concatMap(() =>
          this.events$.pipe(
            map((events: IEvent[]) =>
              events.filter((e) => e.idEvent !== event.idEvent)
            )
          )
        )
      );
  }

1 Answers1

0

Your concept is wrong.

  • You never unsubscribe! So you create memory leak many times as you call checkAll.
  • Not need subject clone/copy.
interface IEvent {
    checked_export: boolean;
}

export class EventService {
    private events$: BehaviorSubject<IEvent[]> = new BehaviorSubject<IEvent[]>([]);
    
    public next(events: IEvent[]): void {
        this.events$.next(events);
    }
    
    public checkAll(): Observable<IEvent[]> {
        return this.events$.pipe(
            map((events: IEvent[]) => {
                return events.filter((event: IEvent) => event.checked_export);
            }),
        );
    }
}

// subscribe with async pipe: <ng-container *ngIf="data$ | async; else nodata">
// subscribe with async pipe: <ng-container *ngIf="isEmpty$ | async">
export class Component {
    nodata: any;
    
    constructor(private service: EventService) {
    }
    
    get data$(): Observable<IEvent[]> {
        return this.service.checkAll();
    }
    
    get isEmpty$(): Observable<boolean> {
        return this.data$.pipe(map((data) => data.length === 0));
    }
}

Update for comment

@Injectable() // config into module's provider!!!
export class DataFromServerResolve implements Resolve<IEvent[]> {
    constructor(private http: HttpClient) {
    }
    
    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<IEvent[]> {
        return this.http.get<IEvent[]>('url'); // then navigate here this resolving all time. Or use Ngrx Store / service to cache
    }
}

// to routing
const routes: Routes = [{ path: '', component: MyComponent, resolve: { myData: DataFromServerResolve } }];

interface IEvent {
    checked_export: boolean;
}

// subscribe with async pipe: <ng-container *ngIf="checkAll() | async; else nodata">
// subscribe with async pipe: <ng-container *ngIf="isEmpty$ | async">
@Component({
    selector: '...',
    templateUrl: '...',
    styleUrls: ['...'],
})
export class MyComponent implements OnInit {
    data: BehaviorSubject<IEvent[]>;
    
    constructor(private route: ActivatedRoute) {
    }
    
    ngOnInit(): void {
        // resolve: { myData: DataFromServerResolve } -> snapshot.data.myData (It will normal: IEvent[] type)
        this.data = new BehaviorSubject<IEvent[]>(this.route.snapshot.data.myData);
    }
    
    // There is not ngDestroy to unsubscribe because "| async" will unsubscribe.
    // If we use .subscribe(...) and save to variable then have to!
    
    get isEmpty$(): Observable<boolean> {
        return this.checkAll().pipe(map((data) => data.length === 0));
    }
    
    public next(newData: IEvent[]): void {
        this.data.next(newData); // it will trigger all subscribers again
    }
    
    // or put service and make input parameter and "this.data" replace by parameter name
    public checkAll(): Observable<IEvent[]> {
        return this.data.pipe(
            map((events: IEvent[]) => {
                return events.filter((event: IEvent) => event.checked_export);
            }),
        );
    }
}
Numichi
  • 926
  • 6
  • 14
  • How to use events$ in template and how to filter from input value? –  Nov 01 '20 at 11:53
  • And how to load data from server to `this.events$`? –  Nov 01 '20 at 12:01
  • How to call `checkAll()` and return modified data? –  Nov 01 '20 at 12:04
  • You can init by Angular resolver. but then I would not put the data into service, only the filter. I will update my answer and create a new block below of original answer. – Numichi Nov 01 '20 at 12:04
  • Is it okay? `public checkAll() { this.events$ .pipe( map((events: IEvent[]) => { return events.map((event: IEvent) => { event.checked_export = !event.checked_export; return event; }); }) ) .subscribe((response) => console.log(response)); }` ? –  Nov 01 '20 at 12:09
  • I repeat myself. The does not unsubscribe, it memory leaks and you will lose subscription reference. Over time, your browser will crash! – Numichi Nov 01 '20 at 12:26
  • if you want console.log use: tap pipe. Example: pipe(tap((x) => console.log(x)) – Numichi Nov 01 '20 at 12:27
  • Thank you for updated question, I have added remark to my qustion ,check please –  Nov 01 '20 at 12:30
  • ofc HttpClient will auto unsubscrible. So it can use without unsubscrible. https://stackoverflow.com/questions/35042929/is-it-necessary-to-unsubscribe-from-observables-created-by-http-methods – Numichi Nov 01 '20 at 12:31
  • I think you have to switchMap, not contactMap. And subscribe replace by toPromise. Then you not have to unsubscribe. https://medium.com/@luukgruijs/understanding-rxjs-map-mergemap-switchmap-and-concatmap-833fc1fb09ff – Numichi Nov 01 '20 at 12:35
  • switchMap change Observable chain. so need a return observable. If you have normal data use of() – Numichi Nov 01 '20 at 12:38