Access directive state and populate ngFor - angular2-directives

I have settings in a backend exposed via a service. It's very common to wire up widgets over and over to settings, so I created a directive to allow setting the unique name of the setting to attach a widget to:
<dropdown appSetting="uniqueName">
I've got the two way binding working.
Now in settings that represent groups there's an enum that defines the options. I'd like to use the bound setting to populate the ngFor for the children:
<dropdown appSetting="uniqueName" #selector>
<option *ngFor="let option of selector.setting.options">
Obviously that doesn't work. I can't access directive state. Most of the things I tried ended up with an undefined ngForOf or the wonderful ExpressionChangedAfterItHasBeenCheckedError. Is there a clean way to leverage the existing directive to populate the children?
(I've tried a custom *appFor, but couldn't quite get it working)

Better Answer:
Directives have an export ability that allows them to be referenced. Just needed to add:
#Directive({
selector: '[appSetting]',
exportAs: 'appSetting'
})
Then access as:
<dropdown
appSetting="uniqueName" #varId="appSetting"
[(ngModel)]="varId.settingValue">
<option *ngFor="let option of varId.options" [value]="option">{{ option }}</option>
</dropdown>
Original Answer
A simple wrapper component did the job giving access to the state of a setting for any object in the template:
<setting-group #setting
[uniqueName]="foo">
<dropdown
[(ngModel)]="setting.settingValue">
<option *ngFor="let option of setting.options">{{ option }}</option>
</dropdown>
</setting-group>
If there's a way to avoid the one-off wrapper for this, please let me know.

Related

How can I use the selected prop in a <select> that has a v-model?

Problem:
I have a <select> that uses a v-model to save the selected options into an array. The problem is that I can't use the selected properties on one of the <options> because the v-model ingores that and instead uses the bound JavaScript as it's source of truth.
What I have tried:
I tried looking up my problem on StackOverflow and I have found two questions but they didn't really help nor explain why and how. Here are the posts I have looked at:
using v-model on makes first option not appear in select - I tried this but nothing changed. My selected appear as selected but it was disabled.
Vue v-model with select input - the accepted answer wants me to define it in data and set it to null but that doesn't work when you take a quick glance at my code.
Code:
<select v-model="payload[index]" type="text">
<option v-if="entry.system_role !== null" selected :value="entry.system_role">
{{ entry.system_role }}
</option>
<option v-for="role in entry.roles" :key="entry.id" :value="role">
{{ role }}
</option>
</select>
That is the section with the select and as you can see the v-model is an array which I simply can't set to null. index is from a higher up v-for that renders the amount of <select>.
I tried setting selected to disabled and :value="entry.system_role" to value="" and leaving it empty (but I need the value of this option). Is there anything I can do to make it work? Maybe with a computed or method?

disable selected option from select using vue

i have this select and i want to make it so that when I press any of the options, the option i pressed gets disabled so the user can see the option he selected easier.
<select v-model="modalIU.inpIdSpecialitate" v-on:change="disableEnable()">
<option :value='null'>.:: fără legătură specialitate ::.</option>
<option v-for="item in allSectiiSpecialitati.filter(el => el.idSpecialitate !== null).sort((a, b) => (a.denumireSpecialitate > b.denumireSpecialitate) ? 1 : -1)" :value="item.idSpecialitate">#{{ item.denumireSpecialitate }}</option>
</select>
I just created a codesandbox example for this functionality, I added a disabled property for each option, and on change event listner, I get the option index, and make it disabled.
https://codesandbox.io/s/elated-smoke-lwcriw?file=/src/components/DisabledOptionOnSelect.vue:351-513
There's no need to add a custom method, you can work with v-model and the data you already have.
Add a :disabled='modalIU.inpIdSpecialitate === item.inpIdSpecialitate' on the option with the v-for, so you conditionally disable the option if it is the one currently selected.
You can then remove the v-on:change="disableEnable()". Also consider adding a :key attribute to your v-for for best practice.

Why are my Vue/Nuxt Select field states valid by default?

I have a variety of HTML select elements inside of Nuxt.js. I'm also using Vuelidate for validation and this is working as expected. This is a typical select box in my form:
<select
id="location"
name="location"
v-model="form.location"
#blur="$v.form.location.$touch()"
:class="{error: appendErrorClass($v.form.location)}"
>
<option :value="null" hidden>Choose...</option>
<option
v-for="(item, index) in $store.state.quotes.data.practiceStates"
:key="index"
:value="item.data">
{{item.display}}
</option>
</select>
Before selecting any of the options, I'm noticing the following on all select fields.
I've tried removing any Vue magic on a test select field to see if the same results happen.
<select id="location1" name="location1">
<option value="" hidden>Choose...</option>
<option value="one">one</option>
<option value="two">two</option>
<option value="three">three</option>
</select>
Still seeing valid: true. Is there anything I'm overlooking that would cause the validity to default to true? Thanks in advance for any help or guidance on this issue.
UPDATE For Clarification:
Vuelidate validation works just fine. The issue I'm dealing with is the select field property Validity.validate. I only mention Vuelidate to give full context.
HTML Select is a sort of "strange" form element in that validity is typically checking to see if there's a readable value. Since a select always has something picked, it validates...
This is different from say a telephone input that has a specific pattern required to be valid.
I haven't used Vuelidate specifically, but I read the docs as saying, if you left the v-model="form.location" there's a good chance it's simply validating that a value exists so Any selcted item would qualify.
In my original post, I referenced the dynamic style based on the vuelidate library: :class="{error: appendErrorClass($v.form.location)}"
#PierreSaid responded to this post and later deleted his/her reply. Turns out, his response was helpful in pointing me to some Vuelidate attributes that were able to assist in a workaround for my issue. Thank you, PierreSaid.
I have since updated that dynamic class to be a mixin that looks like this:
export default {
methods: {
appendErrorAndValidityClass(field) {
return {
error: field.$error,
invalid: field.$invalid,
};
}
}
};
After importing the mixin into the Vue partial, my select box looks like this:
<select
id="location"
name="location"
v-model="form.location"
#blur="$v.form.location.$touch()"
:class="appendErrorAndValidityClass($v.form.location)"
>
This appends a class of invalid when the select field has not been updated. It also updates the error style accordingly. Now, I can assign styles for when the select field has an invalid class. This solves the overall issue for me. Thanks for your help PierreSaid and Bryce.

Conditionally Add Attribute

Is it possible to conditionally add an attribute to an element using binding syntax? I am aware of if.bind, but that targets elements. Rather I am interested in targeting a specific attribute on an element.
Example:
<a href.bind="model.link">${model.text}</a>
If model.link is falsy, then I don't want the href at all--just treat the <a /> as a container element.
I realize I could create two <a /> tags--one with the attribute and one without--and use an if.bind on both, but that seem clunky and un-aurelia like.
I don't think it's supported in Aurelia currently (issue 1, issue 2)
This,
<a href.bind="addLink ? link : ''">Link</a>.
will produce
<a href>Link</a>
if addLink is false.
It won't remove the attribute entirely. If you are using a library which will check the existence of an attribute to manipulate the element, then this won't work. Another option would be to create a custom attribute like this. But that seems like an overhead.

Vue.js—Difference between v-model and v-bind

I'm learning Vue with an online course and the instructor gave me an exercise to make an input text with a default value. I completed it using v-model but, the instructor chose v-bind:value and I don't understand why.
Can someone give me a simple explanation about the difference between these two and when it's better use each one?
From here -
Remember:
<input v-model="something">
is essentially the same as:
<input
v-bind:value="something"
v-on:input="something = $event.target.value"
>
or (shorthand syntax):
<input
:value="something"
#input="something = $event.target.value"
>
So v-model is a two-way binding for form inputs. It combines v-bind, which brings a js value into the markup and v-on:input to update the js value. The js value must be present in your data, or in an inject.
Use v-model when you can. Use v-bind/v-on when you must :-) I hope your answer was accepted.
v-model works with all the basic HTML input types (text, textarea, number, radio, checkbox, select). You can use v-model with input type=date if your model stores dates as ISO strings (yyyy-mm-dd). If you want to use date objects in your model (a good idea as soon as you're going to manipulate or format them), do this.
v-model has some extra smarts that it's good to be aware of. If you're using an IME ( lots of mobile keyboards, or Chinese/Japanese/Korean ), v-model will not update until a word is complete (a space is entered or the user leaves the field). v-input will fire much more frequently.
v-model also has modifiers .lazy, .trim, .number, covered in the doc.
In simple words
v-model is for two way bindings means: if you change input value, the bound data will be changed and vice versa.
but v-bind:value is called one way binding that means: you can change input value by changing bound data but you can't change bound data by changing input value through the element.
check out this simple example: https://jsfiddle.net/gs0kphvc/
v-model
it is two way data binding, it is used to bind html input element when you change input value then bounded data will be change.
v-model is used only for HTML input elements
ex: <input type="text" v-model="name" >
v-bind
it is one way data binding,means you can only bind data to input element but can't change bounded data changing input element.
v-bind is used to bind html attribute
ex:
<input type="text" v-bind:class="abc" v-bind:value="">
<a v-bind:href="home/abc" > click me </a>
v-model is for two way bindings means: if you change input value, the bound data will be changed and vice versa. But v-bind:value is called one way binding that means: you can change input value by changing bound data but you can't change bound data by changing input value through the element.
v-model is intended to be used with form elements. It allows you to tie the form element (e.g. a text input) with the data object in your Vue instance.
Example: https://jsfiddle.net/jamesbrndwgn/j2yb9zt1/1/
v-bind is intended to be used with components to create custom props. This allows you to pass data to a component. As the prop is reactive, if the data that’s passed to the component changes then the component will reflect this change
Example: https://jsfiddle.net/jamesbrndwgn/ws5kad1c/3/
Hope this helps you with basic understanding.
There are cases where you don't want to use v-model. If you have two inputs, and each depend on each other, you might have circular referential issues. Common use cases is if you're building an accounting calculator.
In these cases, it's not a good idea to use either watchers or computed properties.
Instead, take your v-model and split it as above answer indicates
<input
:value="something"
#input="something = $event.target.value"
>
In practice, if you are decoupling your logic this way, you'll probably be calling a method.
This is what it would look like in a real world scenario:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input :value="extendedCost" #input="_onInputExtendedCost" />
<p> {{ extendedCost }}
</div>
<script>
var app = new Vue({
el: "#app",
data: function(){
return {
extendedCost: 0,
}
},
methods: {
_onInputExtendedCost: function($event) {
this.extendedCost = parseInt($event.target.value);
// Go update other inputs here
}
}
});
</script>