Search code examples
angulartypescriptangular7angular-servicesbehaviorsubject

Angular 7: Service instance working incorrectly


I have two components in my app - 'filter' and 'home'. These two components are child components of app component.

I have a below object of which type I would like to pass from filter component to home component via a service. And as I want this data to updated in home component whenever it changes in filter component, I use BehaviorSubject in service.

Model: filter-response.model.ts

export interface FilterResponse{
    filter:string;
    filterlist:string[];
}

filter.component.ts

export class FilterComponent implements OnInit {
    filterMainList:FilterResponse[]=[
        {
            "filter": "abc",
            "filterlist": ["a","b","c"]
        },
        {
            "filter": "def",
            "filterlist":["d","e","f"]
        },
        {
            "filter": "ghi",
            "filterlist": ["g","h","i"]
        }
    ];
    constructor(private filterService:FilterService,) {
        this.filterService.setFilterConditions(this.filterMainList);
    }
    ngOnInit() {
    }
}

In home component I receive the data and I would like to make some changes to it.

home.component.ts

export class HomeComponent implements OnInit {
  constructor(private filterService:FilterService) {
    this.filterService.getFilterConditions().subscribe((data)=>{
      let tc:FilterResponse[]=data
      for(let f in tc){
        tc[f].filterlist=['changes'];//this is where I make the changes
      }
    });
   }
  ngOnInit() {
  }
}

I have a filter service which has the behavior subject. This is where I got confused. I log the data here in service before next(), And the data I actually get here is which I change in home component, which means the changes I make with the instance of data in home component is also affecting(changing) the instance of data in service.

filter.service.ts

@Injectable({
    providedIn:'root'
})
export class FilterService{
    private filterConditions = new BehaviorSubject<FilterResponse[]>(null);
    constructor(){}
    setFilterConditions(filterList:FilterResponse[]){
        console.log(filterList);//data changes incorrectly
        this.filterConditions.next(filterList);
    }
    getFilterConditions():Observable<FilterResponse[]>{
        return this.filterConditions.asObservable();        
    }
}

My console log:

console log

The data that should actually be the one I just passed in the filter component. But it displays the data that I modify in home component.

And finally app.component.ts, if it could help

app.component.ts

<app-filter></app-filter>
<app-home></app-home>

Another thing I would like to bring to your notice is. I tried to recreate the scenario in stackblitz. But the twist is it working fine there.

https://stackblitz.com/edit/angular-ivy-9th5pj

Kindly explain what I am missing in my project. Or is this the actual way the service will work in angular.


Solution

  • I misunderstood your problem: I thought that your question was why is the console.log output showing a modified array while being placed before the modification.

    Like explained in khush's answer, non primitive types are passed by reference: JS does not pass a copy of the array to functions/methods, but the array itself. So that's the same array instance (filterMainList) that is passed with next and that is modified in your HomeComponent.

    Now, you have console.log(filterList) before the modification of the data, but the log output still shows the changes. This is because when you click on the Array(3) in your browser's console, the debugger evaluates the value of the variable at this time , which has already been modified by then.

    This is not visible on your stackblitz example because you don't log the array, but JSON.stringify(arr). In this case, you don't need to expand the value in your console, the value is already displayed as a string and won't be re-evaluated. If we just log the variable, the stackblitz demo will reproduce the same 'problem'

    If this behaviour is not the desired one, you can clone the data passed like khush's suggested; either from the component sending it, or directly in your service before calling next, which you'll only have to do once if you have multiple calls in your code to setFilterConditions