Collaborative service involves objects passing through reference

I am encountering an issue with a shared service in angular. Upon application startup, the init function is triggered, fetching and returning data that is vital across the entire application.

Components have the ability to inject this service and retrieve the data; however, the data can be altered within the component as well. In my current setup, every modification made to the object within my component reflects on the object returned by the shared service.

@Injectable()
export class SharedDataService {
   private _object1: SomeType[];
   private _object2: SomeType[];
   private _object3: SomeType[];

   constructor(private _http: HttpClient, @Inject('BASE_URL') private _baseUrl: string) {}

   public init() {
       this._http.get<IInitModel>(this._baseUrl + 'init').subscribe(
            (r: IInitModel) => {
                this._object1 = r.object1;
                this._object2 = r.object2;
                this._object3 = r.object3;
            }
   }

   public getObject1(){
       return this._object1;
   }

   public getObject2(){
       return this._object2;
   }

   public getObject3(){
       return this._object3;
   }

The init() function is executed during app startup to acquire essential data for the application. Within my components, I access this data like so:

export class SomeComponent {
    public object1: SomeType[];

    constructor(private _sharedDataService: SharedDataService) {}

    ngOnInit() {
       this.object1 = this._sharedDataService.getObject1();
    }
}

If I make changes to the object in the ngOnInit method of my component

this.object1.push({ prop1: 1, prop2: "SomeValue" })

The private member's value in the shared service also gets altered.

console.log(this._object1) // Output from shared service: [{ prop1: 1, prop2: "SomeValue"}]
console.log(this.object1) // Output from component injecting the service: [{ prop1: 1, prop2: "SomeValue"}]

Is this behavior expected? Are objects passed by reference when returned as implemented in my service?

Could someone suggest a more efficient approach to address this scenario?

Answer №1

When working with JavaScript, it's important to understand that objects and arrays are set as references.

For instance:

const obj1 = { name: 'Surjeet' }; // You have obj1 with a name property
const obj2 = obj1; // by assigning, you're setting the reference of obj1 to obj2
obj2.name = 'Yuvi'; // if you change obj2.name, it will also reflect on obj1.name
console.log(obj1.name) // It will display Yuvi
console.log(obj1.name) // It will still show Yuvi

Therefore, in such scenarios, it's essential to create a deep copy of your object. This can be done using the

spread operator or tools like lodash
.

Using Spread Operator

const arr = [1,2,3,4]
const copiedArr = [...arr];

Using Lodash

const arr = [1,2,3,4]
const copiedArr = _.cloneDeep(arr);

In your specific case, modify it as shown below

export class SomeComponent {
    public object1: SomeType[];
    constructor(private _sharedDataService: SharedDataService) {}
    ngOnInit() {
         this.object1 = _.cloneDeep(this._sharedDataService.getObject1());

         //OR

         //this.object1 = [...this._sharedDataService.getObject1()]; 
    }
}

Answer №2

If you want to duplicate your object, consider using lodash library.

https://lodash.com/docs/4.17.15#cloneDeep

@Injectable()
export class SharedDataService {
   private _object1: SomeType[];
   private _object2: SomeType[];
   private _object3: SomeType[];
   constructor(private _http: HttpClient, @Inject('BASE_URL') private _baseUrl: string) {}

   public init() {
       this._http.get<IInitModel>(this._baseUrl + 'init').subscribe(
            (r: IInitModel) => {
                this._object1 = r.object1;
                this._object2 = r.object2;
                this._object3 = r.object3;
            }
   }

   public getObject1(){
       return _.cloneDeep(this._object1);
   }

   public getObject2(){
       return _.cloneDeep(this._object1);
   }

   public getObject3(){
       return _.cloneDeep(this._object1);
   }
}

Another option is

Object.assign(target, ...sources)
. This method allows you to copy enumerable own properties from multiple source objects to a target object and returns the target object.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Setting attributes within an object by looping through its keys

I define an enum called REPORT_PARAMETERS: enum REPORT_PARAMETERS { DEFECT_CODE = 'DEFECT_CODE', ORGANIZATION = 'ORGANIZATION' } In addition, I have a Form interface and two objects - form and formMappers that utilize the REPOR ...

A guide on distinguishing between two dates in Ionic3 using either date-fns or Moment libraries

How can I calculate the difference between two dates to determine the end of an employee's service? Similar Question: What is the best way to find the day difference between two dates using Ionic 3? I am looking for a solution on how to get the exac ...

Angular FormControl is a built-in class that belongs to the Angular forms module. It

I’ve been working on adjusting tslint for the proper return type, and here’s what I have so far: get formControls(): any { return this.form.controls; } However, I encountered an error stating Type declaration of 'any' loses type-safet ...

Converting md ElementRef to HtmlElement in Angular 2+: A Step-by-Step Guide

My query is related to retrieving the favorite food input in TypeScript. The input field is defined as: <input mdInput #try placeholder="Favorite food" value="Sushi"> In my TypeScript file, I have accessed this input using: @ViewChild('try ...

issue with mat-select in a dialog not functionering correctly

In my current project, I am working on implementing a feature that requires the user to select an option from a drop-down menu and confirm their choice by clicking an "OK" button. To achieve this functionality, I am utilizing Angular Material. However, I h ...

Issue with bundling project arises post upgrading node version from v6.10 to v10.x

My project uses webpack 2 and awesome-typescript-loader for bundling in nodejs. Recently, I upgraded my node version from 6.10 to 10.16. However, after bundling the project, I encountered a Runtime.ImportModuleError: Error: Cannot find module 'config ...

Tips for successfully passing function variables as parameters to Angular 2 HTTP subscribe callbacks

I attempted this.propositionService.addProposition(this.proposition) .subscribe(this.addSuccessCallback, this.addFailureCallback); The issue I am encountering is that both addSuccessCallback and addFailureCallback do not have acces ...

React with TypeScript presents an unusual "Unexpected token parsing error"

Recently, I encountered an issue with my helper function that was originally written in JavaScript and was functioning perfectly. However, as soon as I introduced TypeScript into the mix, strange behaviors started to surface. Here is the snippet of code fr ...

Broken Encoding Issue with Server-Side HttpClient Response in Angular 5 Universal

I have encountered an issue with Angular 5 Universal and server side rendering (ssr). When I make a GET request using HttpClient on the server side, the response seems to have encoding problems. However, the same code works perfectly fine on the client sid ...

The Angular 4 proxy configuration file is not properly directing to the specified target destination; instead, it is redirecting to localhost:4200

I'm having trouble with my Angular 4 proxy configuration file - it's not redirecting to the target I specified and instead is redirecting to <http://localhost:4200>. ...

"Take control of FileUpload in PrimeNG by manually invoking it

Is there a way to customize the file upload process using a separate button instead of the component's default Upload button? If so, how can I achieve this in my code? Here is an example of what I've attempted: <button pButton type="button" ...

Disabling eslint does not prevent errors from occurring for the unicorn/filename-case rule

I have a file called payment-shipping.tsx and eslint is throwing an error Filename is not in camel case. Rename it to 'paymentShipping.tsx' unicorn/filename-case However, the file needs to be in kebab case since it's a next.js page that s ...

Preserve data even after refreshing the browser in Angular

Is there a reliable method to preserve my data after refreshing my browser? I've experimented with rxjs behavior subject, but it hasn't worked for me. I prefer not to use local/session storage due to security concerns and best practices, especia ...

Utilizing Eithers to effectively manage errors as they propagate through the call chain

I'm delving into functional programming and exploring different ways to handle errors beyond the traditional try/catch method. One concept that has caught my attention is the Either monad in various programming languages. I've been experimenting ...

Is it possible to generate a Date object from a predetermined string in typescript?

I have a string with values separated by commas, and I'm trying to create a Date object from it. It seems like this is not doable -- can someone verify this and provide a solution if possible? This code snippet doesn't work : let dateString=&a ...

Typescript: The art of selectively exporting specific types

As I develop a Typescript component library, the API consists of two named exports: the component itself and a helper function to create an object for passing as a prop. The process is straightforward. For this library, I utilize an index.ts file as the m ...

Angular: The type '"periodic-background-sync"' cannot be assigned to type 'PermissionName'

I am trying to enable background sync, but I keep encountering an error when I try to enter the code. Why can't it be found? Do I need to update something? This is my code: if ('periodicSync' in worker) { const status = await navigato ...

Tips for selecting the best className type for material-ui components

Currently, I am integrating material-ui into a react app that is built using typescript. Within the material-ui framework, there is a feature called withStyles which allows styles to be injected into a component through its className. However, I am facing ...

How to check all checkboxes in Angular using ngFor and ngIf?

Is there a way to select all checkboxes in an Angular form that uses ngFor and ngIf? I want to activate all checkboxes for the months when I click on "Select All". The list of months is stored in an array. Click here to see the HTML representation of the ...

The ChartistJs is unable to find the property '_index' of an undefined value, either being 0 or null

I recently incorporated chartistJS to display charts on my website. I have successfully implemented the 'onClick' function provided by chartist. Here is a snippet of the code I used: public horizontalBarchartoptions = { 'onClick&apo ...