0

I have a view component that fetches the data model from a service and sets it as a property on itself. Then, a set of custom components recieve the fields of the property as input to display within them. This works as expected.

<app-textbox [caption]="'First name'"
             [value]="data.name.first"></app-textbox>

ngOnInit() {
  this.service.getData()
    .subscribe(res => this.data = res);
}

When the custom component gets its contents edited, I noticed that the original model isn't updated. After some googling I found that I need to bind it two-way and tried the following (banana-box notation it's called apparently). Regrettably, it sems not to have any effect and the original model isn't changed with the new entry.

<app-textbox [caption]="'First name'"
             [(value)]="data.name.first"></app-textbox>

I also tried to apply ngModel as shown below but that led to error message saying that No value accessor for form control with unspecified name attribute. Checking the docs gives me an idea hwo it's supposed to work but not quite as detailed as I need.

<app-textbox [caption]="'First name'"
             [(ngModel)]="data.name.first"></app-textbox>

I need a pointer to where I'm going wrong. Is it somewhere in the custom component that I need to emit out the value? Do I have to use a form? The only idea to resolve it I have now is to label all the controls and collect the values manually. That's obviously a bad practice.

DonkeyBanana
  • 3,266
  • 4
  • 26
  • 65

2 Answers2

2

yes you are right you need to emit event from child component and you need to add suffix "Change" to name of your event. In your case it should be

@Output() valueChange = new EventEmitter<string>();

only then angular will recognize [()] syntax.

Aviso
  • 695
  • 4
  • 12
  • Are you saying that if I have `@Input() beep` and emit from `@Output() beepChange`, then Angular automagically works it out for me? It sounds awesome but I haven't seen any info in that regard. Where is it documented? And can I use some other suffix than *Change*? – DonkeyBanana Aug 18 '19 at 19:14
  • yes it works perfectly you just need to use "[()]" syntax and no you can't change the suffix. – Aviso Aug 18 '19 at 19:35
  • https://codesandbox.io/embed/angular-m69cp look at this example i have created for you.Value in app component will change automatically after 5 seconds. – Aviso Aug 18 '19 at 20:02
  • Well, I'll be darned. It seems to work! Do you have any reference that documents that behavior? It seems like there's no need to implement the *ControlValueAccessor* but that's the most widely spread suggestion for this. How did you learn abou it? – DonkeyBanana Aug 18 '19 at 20:03
  • I read an article about it long ago but coudn't find it now :p – Aviso Aug 18 '19 at 20:18
  • Hehe, I'm too curious and cautious to use a cool feature like that without being able to secure that it's supported for the future. So [I asked about it](https://stackoverflow.com/questions/57552619/cant-find-documentation-on-a-cool-angular-feature-for-two-way-binding) and I hope someone knows. At any rate - it's awesomely smooth and spot-on what usually is needed. – DonkeyBanana Aug 19 '19 at 08:02
  • I asked and got a [very clear answer](https://stackoverflow.com/a/57552714/8744999). – DonkeyBanana Aug 19 '19 at 15:20
  • cool! and The only downside I can see is that you cant handle that event "Change" in parent component. – Aviso Aug 19 '19 at 15:31
1

Your component either has to be a form control and implement ControlValueAccessor. You can read more about it for example here https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html. That's in the case if your component does some "input-ish" stuff (type and display text).

Or you should add an @Input() and an @Output() to your component and handle it in the way you need. In that case your binding will be split and the template would look something like this <app-textbox [caption]="'First name'" (change)="handleChange($event)"></app-textbox>. In this example the @Input() is caption and the @Output() is change.

igor_c
  • 1,200
  • 1
  • 8
  • 20
  • Aha! So was barking up the right binary tree, hehe. My component is input'ish, you're fully correct there. If I choose the second approach and emit a change, will that be by convention recognized by Angular (as long as it's called precisely *change*) or do I still need to manually map the change emitted into the model of the view component? (I'm thinking about the solution suggested by Krishna. Haven't tested it yet but it seems almost too good to be true and I suspect there's more to it than that.) What do you think? – DonkeyBanana Aug 18 '19 at 19:12
  • @DonkeyBanana you can name it whatever you want. `change` or `valueChange` both make sense. I don't think the suffix `change` automatically creates the connection you're talking about. As far as I know it only works with `ngModel` so `[(ngModel)]` can be split into `[ngModel]` and `(ngModelChange)`. In my example you have to add `@Output() change = new EventEmitter()` to your component's template. And then emit changes when needed. – igor_c Aug 18 '19 at 19:18