TNG / ngqp

Declaratively synchronize form controls with the URL
https://tng.github.io/ngqp
MIT License
81 stars 8 forks source link

How to have custom values in select dropdowns? #175

Closed steinsag closed 3 years ago

steinsag commented 3 years ago

In my form, I have a HTML select. The options have a custom value (UUIDs). However, which I normally set in template as follows:

<select class="custom-select" id="valueListKey" [formControl]="selectedKey" type="text" queryParamName="keyId">
    <option value=""></option>
    <option *ngFor="let key of keys" value="{{ key.id }}">
            {{ key.name }}
    </option>
</select>

In my component, I got:

selectedKey = new FormControl('');
this.paramGroup = queryParamBuilder.group({
    keyId: queryParamBuilder.stringParam('keyId', { emptyOn: '' }),
});

But when this is rendered in the browser, the options have a new value 1, 2, 3, ...

So I guess I somehow need to tell that I'm using custom values for my options. But how do I do that?

Airblader commented 3 years ago

Hi!

The ControlValueAccessor shipped with ngqp uses a numeric counter for the IDs and then correlates that back to the value (see e.g. https://github.com/TNG/ngqp/blob/master/projects/ngqp/core/src/lib/accessors/select-option.directive.ts#L24). This works on its own, since in the URL you can see that your custom value is used, not the numeric one.

The problem arises because you're also binding a FormControl against the select, and Angular's FormControl will inspect the value property (which is modified to the numeric counter by ngqp). Binding both Angular Forms controls and ngqp against the same element is problematic for some reasons, this is one of them, which is why it is discouraged.

If you really do need to have the value in a FormControl, instead of binding it to the element through the formControl directive, you can manually sync the values into it through something like

this.paramGroup.get('keyId').valueChanges.subscribe(v => this.selectedKey.setValue(v));

But in general I would recommend working directly with the QueryParamGroup itself and not proxy it through a form control.

Airblader commented 3 years ago

Side note: you could also avoid this by providing your own ControlValueAccessor, of course. Just like Angular Forms, ngqp will prioritize custom CVAs over its built-in ones when available.

steinsag commented 3 years ago

Thank you @Airblader for your valuable feedback. So in general: When using NGQP, I shouldn't use reactive forms at all? Is that correct? This could also explain some other "problems" I'm seeing like that initial param values not being added to the form controls.

Airblader commented 3 years ago

Yes, that is correct. And I understand that it's unfortunate, but the way reactive forms and the CVA infrastructure work I can't see a way to make it work smoothly.

In the past I have been considering a separate API that makes it less/no boilerplate to synchronize reactive form controls from ngqp controls, and I'd still be open to doing so.

The original use case for ngqp when I wrote it was actually entirely orthogonal to reactive forms: either you want to submit something or you just want to synchronize to the URL, but not both. A submitted form synchronized to the URL would only have to do so on submission anyway, which is a much easier version of the problem ngqp solves (of doing so "in realtime"). I'm happy to support other uses cases where sensible, but a direct integration with reactive forms isn't something I think is possible until Angular has rewritten forms from scratch (and even then we'll have to see). But again, by programmatically synchronizing the param group to the reactive forms you can solve all of this for now, it's just a bit more work.

Airblader commented 3 years ago

Just to highlight it one more time, though: unless you have a really good reason to, maybe you don't even need a FormControl at all. ngqp offers much of the same APIs.

steinsag commented 3 years ago

Mmmh, for me the most important benefit of using reactive forms are the built-in input validators. I got a lot of logic in that part of the code, so in my specific case I guess it will have to stick to reactive forms and come up with a custom solution for updating query params. But I will take a deep look at how NGQP keeps track of the query params :-)

Again, thank you for this great module and your time and support!

Airblader commented 3 years ago

This is somewhat related to #123. I didn't originally intend for something like "validation" because the core design decision for ngqp was to treat the URL as the source of truth, and a URL we don't really have control over (in the sense that a user can open any URL they want). That thinking is probably not really accurate, though, because of course a control can be invalid and still have that invalid value as its value (that's how Angular forms work, too).

I think this can still be done by wiring up either ngqp or the Angular form control and programmatically synchronizing the other one. It's boilerplate-y and not ideal, but if you need some of the options of ngqp it's probably easier to manage than setting it up yourself still (as mentioned, an API to make this boilerplate go away is something I'd be open for, too).

Again, thank you for this great module and your time and support!

Thanks! And sorry that this doesn't seem to be a great fit out of the box for this case. I really wish integrating with Angular forms would be easier. ;-)

I will close this issue for now given that it's an answered question, but of course feel free to keep replying / asking or opening new issues!

steinsag commented 3 years ago

Sometimes sleep helps :-) Of course I can use NGQP, because for my list views with quick filters, I don't have validators. And in my forms to edit or create objects, I don't want to store this information in query params. So it works perfectly fine now after I removed the FormControls!

Airblader commented 3 years ago

Awesome! And exactly what ngqp was designed for. Glad it worked out in the end!