3

Let's say I have a component that takes in a Foo instance and displays a form to edit it.

export class ChildComponent implements OnInit {
  @Input() foo : Foo;
  @Output() onChange : EventEmitter<Foo> = new EvenEmitter<Foo>();

  constructor() {
  }

  ngOnInit() {
  }
}

I nest that ChildComponent inside a ParentComponent.

<div id="parent">
  <app-child-component [foo]="parentFoo"></app-child-component>
</div>

Now even though I used 1-way binding here, since foo is an object and thereby passed by reference, any changes made to it in ChildComponent are also reflected in ParentComponent.

How can I prevent that? Is there a way to pass by value? Any best practices?

Johannes Stricker
  • 1,701
  • 13
  • 23
  • 1
    Possible duplicate of [Angular2: Pass by reference to interact between components](https://stackoverflow.com/questions/40260158/angular2-pass-by-reference-to-interact-between-components) – santosh singh Feb 01 '18 at 15:05
  • 1
    You can't pass by value, but you can do a deepcopy of the foo object in the ngInit and then introduce the new object into the html... [about the deep copy](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript) – Noki Feb 01 '18 at 15:07
  • 1
    I know how to deepcopy an object in javascript and I also understand that objects are passed by reference in javascript and that this is not angular related. However, I thought there might be an angular specific way to handle this, because I'm sure I'm not the first one to run into this issue. – Johannes Stricker Feb 01 '18 at 15:14
  • 1
    Hello -- consider marking an answer as accepted if it answered your question or upvoting if you thought it was helpful, so other people know your question has been answered / if an answer was useful to you. No worries if you don't feel like my answer answered your question / was helpful, just wanted to make you aware of the functionality. Welcome to SO :) – vince Feb 01 '18 at 17:49

4 Answers4

7

Alright, my best solution so far looks like this:

export class ChildComponent implements OnInit {
  private _foo : Foo;
  @Input()
  set foo(value : Foo) { this._foo = Object.create(value || null); }
  get foo() : Foo { return this._foo; }

  @Output() onChange : EventEmitter<Foo> = new EventEmitter<Foo>();

  constructor() {
  }

  ngOnInit() {
  }
}

This does seem to work, however it's a lot of code for a single attribute. I still don't understand why there is no such functionality build into Angular (for example an @InputByValue decorator).

I thought that when I want changes to be reflected back from child to parent, I would use two-way-binding [(foo)]="foo". Am I missing something here? Is there any reason not to do what I'm trying to do?

Johannes Stricker
  • 1,701
  • 13
  • 23
3

You need to generate a new object instead of directly using the input reference. A quick solution is to use the Spread syntax to generate a new copy of the object and use it in the template.

@Input('foo') fooRef : Foo;
foo: Foo;
.
.
.
ngOnInit() {
    // creating a new object using the spread syntax
    this.foo = {...this.fooRef};
}
wizAmit
  • 310
  • 1
  • 8
  • 3
    Code dumps without any explanation are rarely helpful. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please edit your question and explain how it works better than what the OP provided. – palaѕн May 09 '20 at 05:52
1

As said by Gunter in this answer: Angular2: Pass by reference to interact between components

Primitive values (string, num, boolean, object references) are passed by value (copied), objects and arrays are passed by reference (both components get a reference to the same object instance).

This has nothing to do with Angular, it's just the way Javascript, and thereby Typescript, works.

In fact, I would say that if you're passing an object into a child component and attempting to change it there, you're updating the same object, so it's sort of "nice" in a way that the values stay in sync.

However, if you want to fork the value, you will need to create a local copy one way or another. As mentioned in the comments, you could do that by doing a deep copy of the object in the child component and then changing that value. Here's a small Stackblitz example illustrating this method: https://stackblitz.com/edit/angular-axr5zf.

vince
  • 7,808
  • 3
  • 34
  • 41
  • Thank you. What confuses me about that is, that this is exactly what 2-way-binding was made for, right? So 1-way-binding shouldn't behave the same way imo. What I'm currently doing is use a `setter()` and `getter()` with the `@Input`, where I just clone the object passed in. I thought about creating a decorator for that, so I don't have to repeat it everywhere, but I have no idea how to do something similar to the `@Input` decorator. – Johannes Stricker Feb 01 '18 at 15:56
  • You're welcome :) That's an interesting idea with the getter / setter and even a custom decorator. Unfortunately, this is just part of using Javascript (Typescript) : / – vince Feb 01 '18 at 15:58
0

You can try const copy = { ...original } For create a copy of your object