0

In my application I use behavior subject to load data :

My service :

  data$ = new BehaviorSubject({
      users: [],
      user: {}
  });

  constructor(​​);

  }​​
  
  dispatch(action: Action): Observable<true | { error: string; }> {
      switch (action.type) {
          case ActionTypes.USER_LIST:
              this.getUsers();
              return of(true);
          default:
              return of(true);
      }
  }

  private getUsers(){
      this.http.get("url")
      .subscribe((users: any) => {
          this.data$.next({...this.data$.value, users: users});
      });
  }

I call this service in my parent component when my child component asks for it.

MyParentComponent :

export class MyParentComponent implements OnInit {
  isLoading = false;
  user$ = new BehaviorSubject(null);

  constructor(private userService: UserService) { }

  ngOnInit(): void {
    
  }
  
  getUsers(event) {
    this.isLoading = true;
    this.userService.dispatch({type: Action Types.USERR_LIST});
    this.user$.next(this.userService.data$);
    this.isLoading = false;
  }
}

MyChildComponent : call load data :

  private getData() {
    this.onDataChange.emit(this.criteria);
    this.dataSource.data = this.items;
    console.log(this.dataSource);
    this.isLoading = false;
  }

Call my child component :

<app-child-comp
                          [entity]="(user$| async)?.value?.userList"
                          (onDataChange)="getUsers($event)" 
                          class="row col-md-12"
>
</app-child-comp>

I don’t understand why my datasource or my items attribute does not update after onEmit whereas when I display the items attribute to json training in my child component it is up to date

totuti
  • 67
  • 1
  • 9
  • Hard to see the link between parent and child... Multiple things look not relevent... `this.user$.next(this.userService.data$);` is odd. You put a BehaviorSubject inside a BehaviorSubject... Inception... Using BehaviorSubject you should make a getter method returning `this.data$.asObservable()`, and your parent component should subscribe to it in its constructor (or use it on your pipe `async`, instead of `user$`)... You should type your variable to see clearer. Here `user$: BehaviorSubject> = new BehaviorSubject(null);` would trigger an obvious strange type... – Random Feb 21 '21 at 12:47
  • Is it possible to have an example please – totuti Feb 21 '21 at 12:49
  • you may have a look here: https://stackoverflow.com/a/57355485/4786273 – Random Feb 21 '21 at 12:54
  • I would like an example directly with the async pipe in the hrml and the particularity here me is that I have more value in my behavior and I would like to recover one in particular – totuti Feb 21 '21 at 12:55
  • I do not find this peculiarity in the exempl – totuti Feb 21 '21 at 13:06
  • If I go as indicated, it does not work. It does not update even with the registration via the subscribe. Help me. It’s been 1 week since I’ve stuck on – totuti Feb 21 '21 at 13:23

1 Answers1

0

According to your code, here is how I would implement it, renaming some stuff and typing some objects for clarity:

Service:

export interface DataUsers {
    users: User[],
    user: User
}

data$ = new BehaviorSubject({
    users: [],
    user: {}
});

constructor(​​);
  // TODO
}​​
  
public dispatch(action: Action): Observable<true | { error: string; }> {
    switch (action.type) {
        case ActionTypes.USER_LIST:
            this.callUsersApi();
            return of(true);
        default:
            return of(true);
    }
}

public getDataUsers$(): Observable<DataUsers> {
  return this.data$.asObservable();
}

private callUsersApi(): void {
  this.http
      .get('url')
      .subscribe((users: User[]) => {
        this.data$.next({...this.data$.value, users: users});
      });
}

Parent component:

// {...}
export class MyParentComponent implements OnInit {
  isLoading = false;
  user$: Observable<DataUsers> = null;

  constructor(private userService: UserService) {
    this.user$ = this.userService.getDataUsers$();
  }

  ngOnInit(): void {
    
  }
  
  getUsers(event) {
    this.isLoading = true;
    this.userService.dispatch({type: ActionTypes.USERR_LIST});
    // this.user$ will automatically receive the new data
    this.isLoading = false;
  }
}

Parent HTML (updated the pipe async):

<app-child-comp
                class="row col-md-12"
                [entity]="(user$| async)?.userList"
                (onDataChange)="getUsers($event)">
</app-child-comp>

Child component:

public ngOnChanges(changes: SimpleChanges) {
  if (changes.entity) {
    // this.entity has changed
    this.isLoading = false;
    if (changes.entity.currentValue) {
      // the new value is not empty
      if (!this.dataSource) {
        // init
        this.dataSource = new MatTableDataSource(changes.entity.currentValue);
      } else {
        // update
        this.dataSource.data = changes.entity.currentValue;
      }
    } else {
      // the new value is null
      if (!this.dataSource) {
        // data were displayed, reset them
        this.dataSource.data = [];
      }
    }
  }
}

private getData() {
  this.isLoading = true;
  this.onDataChange.emit(this.criteria);
  // you cannot set dataSource here, the HTTP call is not ended yet
  // when HTTP call will be ended, the ngOnChanges will be triggered because the parent will inject a new value to this.entity
  // so this.isLoading will be falsed in ngOnChanges, not here.
}
Random
  • 3,158
  • 1
  • 15
  • 25
  • Sorry but this solution still doesn’t work. The data update in my Parent component but not in the child component – totuti Feb 21 '21 at 15:42
  • this.dataSource always remains empty – totuti Feb 21 '21 at 15:43
  • Yet when I display the @Input variable in the child component directly in json mode the data is there. I have the impression that the getData() method of the child component ends before receiving the updated data – totuti Feb 21 '21 at 16:01
  • Of course it does... I didn't see it... I'll update my answer for child component logic – Random Feb 21 '21 at 16:09
  • Just wrote the update, you may have a look – Random Feb 21 '21 at 16:23
  • Thank you it works. But I just didn’t get it. Why is it better to work with obsevable than behavior subject here – totuti Feb 21 '21 at 16:47
  • 2
    You do work with BehaviorSubject. But you should never subscribe to a BehaviorSubject. You have to subscribe to your BehaviorSubject.asObservable() object instead. It makes a simplified copy of your BehaviorSubject, where you can only Subscribe, you cannot write into it. It avoids a component doing `this.userService.getData$().next('evil-data');`. If you want a component to inject a value in your BS, make a setter in your service. – Random Feb 21 '21 at 16:57
  • So what happens here: Your child triggers an event `onDataChange`. In an other thread, your parent will receive this event, and trigger an HTTP call via the userService. in an other thread, the HTTP call will respond, and trigger a `.next()` on your BS. In an other thread, the pipe async will receive this event, and send it to your child component. Again in an other thread, the ngOnChanges of the child will be triggered with that data, and update the dataSource. That notion of "thread" is important. All this is (very) asynchronous, which your initial child `getData()` didn't manage. – Random Feb 21 '21 at 17:00