2

I am creating reusable table component which will enable editing of objects fields to prepare them for sending to the API.

Having an object:

person: {
  name: "John"
  job: {
    type: "IT"
    title: "Software Engineer"
  }
}

I would like to pass the object nested field to a component and edit. F.e:

<edit-field [field]="person.job.title"></edit-field>

Which results in an input field that edits exactly the title field in original object. The problem is that person.job.title is a string, not and object or array so it's not passed by reference.

I had 2 ideas haw the problem could be solved: 1) pass "person.job.title" as a string:

<edit-field [field]="'person.job.title'"></edit-field>

or

<edit-field field="person.job.title"></edit-field>

And in component class do split by a ".":

let fields = this.field.split('.');

and then do a while loop to access the field by reference.

We could also do 2 inputs:

<edit-field-component [fieldRef]="person.job" [field]="'title'"></edit-field-component>

and then inside component do this.fieldRef[this.field]

I am wondering if there is any other, more clean, way to achieve that.

Tukkan
  • 1,574
  • 2
  • 18
  • 33
  • Why are you not setting the value directly person.job.title = "Software architect" :) – PPB Apr 23 '17 at 10:50
  • As I want to make it resuable I don't want to write person.job.title = "foo" in component body. I would like to pass what i want to edit in input fields. If I will pass "person.job.title" as an input, it will be passed as a string, not a reference to an object, isn't it? – Tukkan Apr 23 '17 at 10:56
  • I suppose you want to modify instantaneously the value of your object from your edit-field componant, that's it ? can you show me the edit-field code ? – Nicolas Law-Dune Apr 23 '17 at 11:02

4 Answers4

5

Basically, you want to accomplish two-way binding - i.e. changes to the object value: eg person.job.title updates your new edit component, but also changes made from your edit component also get reflected back to the object value.

In Angular, that means you have to bind both ways:

<edit-field [field]="person.job.title"  (change)="person.job.title=$event"></edit-field>

where your edit-field component has an @Output property that emits the changed value whenever someone types into it. The value emitted from the @Output property will be in the variable $event and you simply want to assign that back to the property that you want to update.

So, your EditFieldComponent can look something like:

@Component({
   .....
   template: `
      <input (input)="change.emit($event.target.value)" ....   />
   `
})
export class EditFieldComponent {
   change = new EventEmitter();
}

The above means that whenever an input event triggers on your input field, the component's change output property will emit the new value of the input field.

===========

If you understand everything above, then Angular provides a little shortcut for this exact scenario. If the output property of your component is named a specific way, you can simplify how you write the two way binding.

So, if your input property is field, and you name your output property fieldChange you can make use of some fancy syntax to cut down how much you have to type.

i.e.

<edit-field [field]="person.job.title"  (fieldChange)="person.job.title=$event"></edit-field>

is equivalent to:

<edit-field [(field)]="person.job.title"></edit-field>
snorkpete
  • 14,278
  • 3
  • 40
  • 57
  • Please take a look at this plunker - http://plnkr.co/edit/RGwJEYp8EikP0VY52yvB?p=preview Do you know what I still do wrong? – Tukkan Apr 23 '17 at 12:34
  • Awesome. That works. Do you have any idea why it doesn't work with [(ngModel)]? – Tukkan Apr 23 '17 at 12:51
  • ngModel adds some async behaviour to help it work, and that always seems to mess up this sorts of things. The new capabilities of Angular makes it easy to recreate two-way binding in any case – snorkpete Apr 23 '17 at 12:54
2

[field]="person.job.title" is one-way binding (changes of person.job.title are propagated to field but not the other way round)

[(field)]="person.job.title" would achieve two-way binding (changes made by fieldChange method are also propagated back to person.job.title)

Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • Please take a look at this plunker - http://plnkr.co/edit/RGwJEYp8EikP0VY52yvB?p=preview Do you know what I still do wrong? – Tukkan Apr 23 '17 at 12:34
1

If you want to reffer your object or property of your object to your component, you need to create an @Output property with type eventEmitter.

@Input('field') field: any;
@Output() fieldChange = new EventEmitter();

Be carefull to name your output property with "Change" suffix word. It will detect automatically change from your object and notify to your main object.

Nicolas Law-Dune
  • 1,631
  • 2
  • 13
  • 30
  • Please take a look at this plunker - http://plnkr.co/edit/RGwJEYp8EikP0VY52yvB?p=preview Do you know what I still do wrong? – Tukkan Apr 23 '17 at 12:34
0

Javascript just like Java is passed by value, they have never offered passed by reference. So in your case, your best option is to pass your person object directly. Even though it will be copied inside your function, the copy still refers to the same object, so changing a field in the copy will also change the corresponding field in the original.

Trash Can
  • 6,608
  • 5
  • 24
  • 38
  • Do you mean something similar to what I wrote in the example? `` – Tukkan Apr 23 '17 at 10:57
  • Just create a `@Input()` field, say `person` then do `` then inside your class, just modify `person` however you like and it will reflect the changes afterwards – Trash Can Apr 23 '17 at 11:00
  • that's what I want to ommit. I would like to edit hundreds of objects and hundreds of fields. I'm trying to achieve more reusable way with one component type. – Tukkan Apr 23 '17 at 11:03
  • If you want to modify an object, pass an object. – Trash Can Apr 23 '17 at 11:06