Polymer 2.0 Data-Binding Array - javascript

I'm trying to show the list of all the candidates under their corresponding parties I have stored in my Firebase database. I am using Polymer 2.0 Starter-Kit.
Here's the code:
_checkParties() {
var candidate_list = []
firebase.database().ref("/candidates/"+this.party.$key).once("value", function(snapshot) {
snapshot.forEach(function(snap) {
var data = {"key": snap.key, "data": snap.val()}
var results = Object.keys(data["data"]).map(function(key) {
return {
name: key,
value: data["data"][key],
key: data["key"]
};
})
candidate_list.push(results[0])
})
})
this.candidates = candidate_list;
}
Basically I'm querying from my firebase the children of that path, then appended them to my var candidate_list. Outside the query I then set my this.candidates to the array var candidate_list.
Here's the code to my template:
<template is="dom-repeat" items="{{ candidates }}" as="candidate">
<div class="card">
<p>[[candidates]]</p>
<h3>[[candidate.name]] - [[candidate.value]]</h3>
<paper-button raised style="color: yellow; background-color: black;" on-tap="_castVote">Vote</paper-button>
</div>
</template>
The code to my static properties:
candidates: {
type: Array,
value: []
}
Then my constructor:
constructor() {
super();
}
I tried adding this to my constructor but it doesn't seem to affect anything:
constructor() {
super();
this.candidates = [];
}
The result should be that the cards in my template dom-repeat show all the candidates. Instead I'm getting nothing is being shown.
But when I console.log I could see all my candidates.

I am not sure where your _checkParties is being called, but it sounds like Polymer isn't aware that you updated the candidates array.
I think that the first attempt could be to use the set method, to be sure it's notified of the update, as described here. So that would mean to replace your last statement in the method. Instead of
this.candidates = candidate_list;
you would have
this.set('candidates', candidate_list);
Another option, that seems to be very well suited for what you're doing would be to have the candidates list as a computed property. So you would just add to your property definition the 'computed' key:
candidates: {
type: Array,
value: [],
computed: '_checkParties(candidatesResponse)',
}
and in your method you would have to return the list, instead of overwritting the property, so
return candidate_list;
instead of the same line mentioned above.

Related

Vue on changing array object trigger get method to check for changes

I have a button component that looks like this
<template>
<button
class="o-chip border-radius"
:class="{
'background-color-blue': theValue.isSelected,
'background-color-light-grey': !theValue.isSelected,
}"
#click="onSelection(theValue)"
>
{{ theValue.displayText }}
</button>
</template>
And when it is pressed it sets it isSelected state and emit an event to the parent component
private onSelection() {
this.theValue.isSelected = !this.theValue.isSelected;
this.$emit('selected', this.theValue);
}
So far so good the issue is in the parent component. Here I go through all the items that is the array that creates the button components above. The .value property is the unique identifier here so what I do is I look through the array with the item sent from the button component, then when found i use splice to trigger reactivity to replace that array object with the one sent from the button component. When i console.log this it works it shows that isSelected property now is true.
private selectedItem(item: Item) {
var index = this.itemSelectOptions
.map((p) => p.value)
.indexOf(item.value);
this.itemSelectOptions.splice(index, 1, item);
console.log(this.itemSelectOptions);
}
But then i have this get method that checks for anyChanges on this array and other things and then render UI based on true/false here. The issue is that when this array get changed the anyChanges method does not react and remains false.
private get anyChanges(): boolean {
console.log(this.itemSelectOptions);
return this.itemSelectOptions!.some((p) => p.isSelected);
}
How do i make it so that the anyChanges get method reacts on the changes made to itemSelectOptions which is also an get function
private get itemSelectOptions(): Item[] {
return this.items
? this.items.map((item) => ({
value: item.id.toString(),
displayText: item.displayName.toString(),
isSelected: false,
}))
: [];
}
What you want is a watcher on itemSelectOptions
watch: {
question(newOptions, oldOptions) {
this.anyChanges(newOptions)
}
},
guess it will look smthing like this ^^
https://vuejs.org/guide/essentials/watchers.html#basic-example
The reason this was not working was that since itemSelectOptions where a get function it should/can not be modified. So changing it into a private variable and initializing it solved it like this.
private itemSelectOptions: Item[] = [];
and then initializing
this.itemSelectOptions = this.items
? this.items.map((item) => ({
value: item.id.toString(),
displayText: item.displayName.toString(),
isSelected: false,
}))
: [];

Why won't my template update with it being bound to a computed property?

I am facing an issue where I have some template HTML in a component that relies on the computed getter of a Vuex method. As you can see in the template, I am simply trying to show the output of the computed property in a <p> tag with {{ getNumSets }}.
As I update the state with the UPDATE_EXERCISE_SETS mutation, I can see in the Vue devtools that the state is updated correctly, but the change is not reflected in the <p> {{ getNumSets }} </p> portion.
Template HTML:
<template>
...
<v-text-field
v-model="getNumSets"
placeholder="S"
type="number"
outlined
dense
></v-text-field>
<p>{{ getNumSets }}</p>
...
</template>
Component Logic:
<script>
...
computed: {
getNumSets: {
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
...
</script>
Vuex Store Logic:
...
state: {
designerBucket: []
},
getters: {
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
state.designerBucket.forEach(exercise => {
if (exercise.id == payload.id) {
exercise.numSets = payload.numSets
}
})
}
}
Any insight is very appreciated!
P.S. I have also tried using a for (var i=0...) loop, looping over the indices and then using Vue.set() to set the value. This did update the value in the store as well, but the computed property is still not updating the template.
This turned into a bit of a long-winded answer, but bear with me.
Here's my hunch: since you're returning a function from your Vuex getter, Vue isn't updating your computed property on state changes because the function never changes, even if the value returned from it would. This is foiling the caching mechanism for computed properties.
Reactivity for Arrow Function Getters
One of the things to keep in mind when creating a getter like this, where you return an arrow function:
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
...is that you're no longer returning actual state data from your getter.
This is great when you're using it to pull something from state that depends on data that's local to your component, because we don't need Vue to detect a change, we just need the function to access current state, which it does fine.
BUT, it may also lead to the trap of thinking that updating state should update the getter, when it actually doesn't. This is really only important when we try to use this getter in a computed property like you have in the example, due to how computed properties track their dependencies and cache data.
Computed Caching and Dependency Detection
In Vue, computed properties are smarter than they first seem. They cache their results, and they register and track the reactive values they depend on to know when to invalidate that cache.
As soon as Vue calculates the value of a computed property, it stores it internally, so that if you call the property again without changing dependencies, the property can return the cached value instead of recalculating.
The key here for your case is the dependency detection– your getter has three dependencies that Vue detects:
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
The getter: this.$store.getters['designer/getNumSetsForExercise']
this.id
this.parent
None of these values change when <v-text-field> calls your setter.
This means that Vue isn't detecting any dependency changes, and it's returning the cached data instead of recalculating.
How to Fix it?
Usually, when you run into these sorts of dependency issues, it's because the design of the state could be improved, whether by moving more data into state, or by restructuring it in some way.
In this case, unless you absolutely need designerBucket to be an array for ordering purposes, I'd suggest making it an object instead, where each set is stored by id. This would simplify the implementation by removing loops, and remove the need for your getter altogether:
...
state: {
designerBucket: {}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
// Need to use $set since we're adding a new property to the object
Vue.set(state.designerBucket, payload.id, payload.numSets);
}
}
Now, instead of invoking a getter, just pull designerBucket from state and access by this.id directly:
<script>
...
computed: {
getNumSets: {
get() {
return this.$store.state.designerBucket[this.id];
},
set(value) {
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
});
}
}
...
</script>
This should allow Vue to detect changes correctly now, and prevent the stale cache problem from before.
Edited: First import mapGetters from 'vuex' like this on the top of the script tag.
import { mapGetters } from "vuex"
Now in your computed object, add mapGetters and pass arguments to the getter method inside the get() method like this:-
computed: {
...mapGetters('designer',['getNumSetsForExercise']),
getNumSets: {
get() {
var numSets = this.getNumSetsForExercise({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
And see if it works.

How to use Vue.js methods to set data properties

I'm new to Vue.js and trying to display data that is requested from a server. I have created a new Vue app and defined a method:
methods: {
getData: function() {
// making a request, parsing the response and pushing it to the array.
console.log(arr);
return arr;
}
}
The method works OK and I can log the array to the console on a button click.
<button v-on:click="getData">Get me some data</button>
However, I'm not sure how to actually use this array in the app. I would like save it to a property and then display it for the user. At first, I was thinking that I could use computed properties like this:
computed: {
values: function() {
return this.getData;
}
}
... and display it to the user with a for loop:
<p v-for="value in values">{{ value }}></p>
At least this solution did not produce the desired result. I've probably misunderstood some of Vue's logic here.
You need to use data property.
data() {
return {
values: []
}
},
methods: {
getData: function() {
this.values = arr;
}
}
And loop the values

vue computed property not able to get data

I am currently experiencing an issue where the computed() property is not able to get data. Although data was already initiated in created() property. Am I doing it wrong? Please advise how I can fix this issue.
const randomPlayers = {
template:
`
<input type="text" v-model="search_player">
<div v-for="player in modPlayers" v-if="list_of_random_players!=null">
<p>{{player.firstname}}</p>
<p>{{player.lastname}}</p>
<div>
`,
props: ['data'],
data (){
return{
list_of_random_players: null,
search_player: null
}
},
created(){
this.get_random_players()
},
computed: {
modPlayers(){
return this.list_of_random_players.filter( person => {
return !this.search_player ||
( person.firstname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1 || person.lastname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1)
})
}
},
methods: {
get_random_players: function(){
$.post(
url:'random_url'
data: {
players: data
}
).done((success)=>{
this.list_of_random_players: JSON.parse(success)
})fail((err)=>{
console.log(err)
})
}
}
}
I get the following two errors:
(1) TypeError: Cannot read property 'filter' of null
(2) TypeError: this.list_of_random_players.filter is not a function
From Vue: "When a Vue instance is created, it adds all the properties found in its data object to Vue’s reactivity system. When the values of those properties change, the view will “react”, updating to match the new values."
So data is a function that returns an object but as mentioned by #Sovalina you are not returning it so you cannot access its properties. You need to add return and change null to []:
data() {
return {
list_of_random_players: [],
search_player: []
}
},
or you can do without return and like a regular object:
data: {
list_of_random_players: [],
search_player: []
}
When your Vue component is used multiple times, it is better to use it like a function(first case).
"When defining a component, data must be declared as a function that returns the initial data object. Why? Because there will be many instances created using the same definition. If we still use a plain object for data, that same object will be shared by reference across all instance created! By providing a data function, every time a new instance is created we can call it to return a fresh copy of the initial data."
Reference:link
It might be just a typo but you need to add : to methods as well.

Angular 2 change detection on array push of objects

I have a 'Schedule' typescript class and a 'selectedSchedule' variable of this 'Schedule' type.
Schedule:
export class Schedule{
scheduleId: string;
sheduleDate: Date = new Date();
scheduleName: string;
jobs: Job[];
unscheduledEmployeesCount: number = 0;
idleEquipmentCount: number = 0;
unscheduledTrucksCount: number = 0;
}
I'm binding this variable and it's properties in the HTML by utilizing interpolation. My problem is I'm using a *ngFor to iterate and display each 'job'...
<div class="job" *ngFor="let j of selectedSchedule.jobs">
<div [ngClass]="{'hasEmployees': j.jobEmployees.length > 0 }">
<div *ngFor="let e of j.jobEmployees">
{{e.employee['name'] !== null ? e.employee['name'] : e.name}}
</div>
</div>
</div>
whenever the user triggers the 'addJob()' method by clicking a button, the newly created 'job' doesn't get detected by Angular and results in the properties of the 'job' to be null, most notably the 'jobEmployees' of each 'job'. I understand Angular doesn't detect changes to an array when you push/remove objects out of the box. I'm looking for a solution to pick up on these changes when a user adds a new job. I have tried to reassign the 'selectedSchedule' and it's 'jobs' property with no success. Also have tried to slice() the jobs array with the thought of 'recreating' the array.
I'm currently making an extra http request to get the schedule's jobs again, which seems to prevent the null property problems, but I'm hoping to find a more efficient solution.
The addJob() method in component:
addJob() {
var newJob: Job;
this.data.addJob(this.selectedSchedule.scheduleId).subscribe(addedJob => {
this.selectedSchedule.jobs.push(addedJob);
this.data.loadSchedules().subscribe(success => {
if (success) {
this.selectedSchedule = this.data.schedules.find(schedule => schedule.scheduleId === this.selectedSchedule.scheduleId);
}
});
});
}
addJob() method in data service:
addJob(scheduleId: string) {
return this.http.get("/api/job/addjob", { headers: this.headers, params: { scheduleId: scheduleId } })
.map((job: Job) => {
return job;
});
}
'Job' class:
export class Job {
jobId: string;
jobName: string;
jobNumber: string;
jobEmployees: Array<Employee> = new Array<Employee>();
jobEquipment: Array<Equipment> = new Array<Equipment>();
jobTrucks: Array<Truck> = new Array<Truck>();
}
You are looking for ChangeDetectorRef,Normally this is done by Angulars change detection which gets notified by its zone that change detection should happen. Whenever you manually subscribe in a component, angular marks the component instance as dirty whenever the subscribed stream emits a value.
Try calling it manually as follows,
this.selectedSchedule = this.data.schedules.find(schedule => schedule.scheduleId === this.selectedSchedule.scheduleId);
this.cdRef.detectChanges();

Categories

Resources