How do I dynamically render an array from an api call in Vue? - vue.js

I'm trying to make an autocomplete dropdown menu in vue and can't get the api response to render on the page. I'm making an api call on every keystroke in the input. You can see in the handleOnChange method that I'm trying to set the response to the results variable that is binding to the list above.
When I console log the results right after I make the api call AND set it to the data binding variable it logs as if everything is working fine. However, it does not render the actual data on the screen.
Here is my code
<template>
<div>
<input type="text" value="" v-model="address" placeholder="Start typing an address..." #input="methods.handleOnChange(address)"/>
<ul v-if="results.length !== 0">
<li v-for="result in results">
{{ result.streetLine || ""}}
</li>
</ul>
<p v-model="results">
{{results}}
</p>
</div>
</template>
<script>
const SmartyStreetsSDK = require("smartystreets-javascript-sdk");
const SmartyStreetsCore = SmartyStreetsSDK.core;
const Lookup = SmartyStreetsSDK.usAutocompletePro.Lookup;
const credentials = new SmartyStreetsCore.SharedCredentials([website-key]);
const clientBuilder = new SmartyStreetsCore.ClientBuilder(credentials).withLicenses(["us-autocomplete-pro-cloud"]).withBaseUrl("https://us-autocomplete-pro.api.smartystreets.me/lookup");
const client = clientBuilder.buildUsAutocompleteProClient();
export default {
name: 'Autocomplete',
data () {
return {
address: "",
results: [{streetLine: "testing"}],
methods: {
handleOnChange: function(address) {
//TODO: need to be able to access "results" from in here
console.log("this: ", this);
if (address) {
const lookup = new Lookup(address);
client.send(lookup).then((response) => {
console.log(response.result);
this.results = response.result;
console.log("databinding: ", this.results);
}).catch(console.log)
}
}
}
}
}
}
</script>

Vue.set()
As discussed in the comments, Vue.set was able to do it.
See documentation: https://v2.vuejs.org/v2/api/#Vue-set
Arguments are:
{Object | Array} target
{string | number} propertyName/index
{any} value
It replaces the value at target[propertyName/index] with value and forces reactivity on the value(s).
In your case it should be this instead of this.results = response.result;:
Vue.set(this, "results", response.result);

Related

AlpineJS async call to render array of objects

Having the following snippet trying to fill an array of object with async alpine call but I can not get any result. Hier is what I try.
HTML:
<div x-init="experts.retrieveList" x-data="experts.list">
<ul>
<template x-for="item in experts.list" :key="item">
<li>
<div x-text="await item.address" ></div>
</li>
</template>
</ul>
</div>
external JS file
window.experts = {
apiUrl: 'http://bdb.local:8991/api/',
data: [],
list: [],
expertsForm: null,
retrieveList: () => {
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`;
experts.apiCalls(membersUrl)
},
filterByParams: () => {
},
apiCalls: async (url) => {
let response = await fetch(url);
experts.list = await response.json()
return experts.list;
},
}
What is wrong in this case?
There are a few errors in this code:
You cannot use just a part of an Alpine.js component in the x-data="experts.list". If you don't define data variables directly in x-data, then it must be a real component that returns data and methods.
You cannot use an object as the key. It must be a string or number, like item.id or something like this.
The x-text="await item.address" seems incorrect. item.address should be a string, that has been already downloaded from the API.
In the component you need to use the this. prefix to access properties and methods.
Assuming your API returns the correct data format, something like this should work:
<div x-data="experts">
<ul>
<template x-for="item in list" :key="item.id">
<li>
<div x-text="item.address"></div>
</li>
</template>
</ul>
</div>
And the component in an external file:
const experts = {
apiUrl: 'http://bdb.local:8991/api/',
data: [],
list: [],
expertsForm: null,
init() {
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`
this.apiCalls(membersUrl)
},
filterByParams() {
},
async apiCalls(url) {
let response = await fetch(url)
this.list = await response.json()
},
}
The init() method is executed automatically by Alpine.js.

Computed Property + Show/Hide DIV

I am not a vue.JS programmer, but have been asked to jump in and make a few changes to an existing app. I am trying to use a computed property to show/hide a DIV.
The problem is the computed property is not being called after the form POST (I put a console.log statement inside the computed method to verify it's not even being called). The findLoginProvider method is invoked successfully and the localStorage items are set... but the DIV is not hidden.
Like I said, I am not a vue.JS programmer and cannot figure out why this isn't working... this is a simplified example of the real page. I really want computed properties to work because I have multiple DIV's to show/hide based on a few computed properties (so manually settings the visibility of a specific DIV is not desired)
HTML:
<div v-if="showFindLoginProvider" :class="{'disabled': isLoading}">
<form #submit.prevent="findLoginProvider" method="POST">
<div>
<label class="block text-gray-700">Email Address</label>
<input
type="email"
name="email"
v-model="email"
placeholder="Enter Email Address"
autofocus
autocomplete
required
/>
</div>
<button type="submit">Continue</button>
</form>
</div>
Methods:
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
localStorage.setItem("login_email", this.email);
localStorage.setItem("login_provider", JSON.stringify(response.data));
} else {
this.isError = "Error retrieving login provider";
}});
this.isLoading = false;
},
Computed:
showFindLoginProvider() {
console.log("showFindLoginProvider")
return !this.isLoggedIn && (!this.hasLoginEmail || !this.hasLoginProvider);
},
According to the docs
A computed property will only re-evaluate when some of its reactive dependencies have changed
By saying that in your fnc findLoginProvider you need to change reactive variable to fire showFindLoginProvider run
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
// will tell computed to run
this.isLoggedIn = true
// rest of your code
} else {
this.isError = "Error retrieving login provider";
}
});
this.isLoading = false;
},

How can i get properties from template

Can i get properties from template before send request to server about the data?
Here's my template:
<box-component inline-template>
<div>
<div class="loading" v-if="Loading"> Loading ... </div>
<div v-if="Result">
<ul>
<li> {{ Result.FirstProp }} </li>
<li> {{ Result.FourthProp }} </li>
</ul>
</div>
</div>
And this is my component:
let BoxComponent = {
data() {
return {
Result: null,
Loading: true
};
},
created: function () {
this.Update();
},
methods: {
Update: function () {
this.Loading = true;
axios.post("#SERVER_URL#")
.then(response => {
this.Loading = false;
this.Result = response.data;
});
}
}
}
Vue.component('box-component', BoxComponent);
It's works fine! The problem is the server response contain much more data. Server response:
{
"FirstProp": "first",
"SecondProp": "second"
...
"HundredthProp": "hudredth"
}
I can't modify the response (other project use this too). But if i am able to send property list from the template (which are in this case FirstProp and FourthProp) the server side give me the filtered response which contains only these properties becouse the optimal response look like this:
{
"FirstProp": "first",
"FourthProp": "fourth"
}
So, the question is: how can i do that?
Can i find it in the Vue object?
I can load template as a string variable, but i want to avoid regex hack.
Update:
In this case, BoxTemplate call server side without "filters"
This is the optimal case, when js get the variables, that the template use, and call with them the server side. In this case, in the response there are only that variables, what template really use
I don't know how is set up your back-end but you can have additional property within your data:
data() {
return {
listOfProperty: [
'FirstProp',
'FourthProp',
],
...
And use list of it to send data to server.

Vue input event not capturing entire field

I have a vue component that adds a search bar and search bar functionality. It contains this line:
<input class="input" type="text" placeholder="Address" v-model="searchQuery" v-on:input="(event) => this.$emit('queryChange', event)">
This captures the text in the search bar and emits it.
In my vue, this triggers my updateSearchQuery function:
this.searchQuery = event.data which merely saves the users input in the searchQuery property in my vue. Everything works fine when I do this, until, I make a search and then, make another call using the same this.searchQuery data.
For example, I'm trying to filter results with the search query '956'. I enter it and this call is made: GET /users?cp=1&pp=20&se=956, just like it should. Then after the page loads, if I go to page 2 of the results, this is the call that is made to the server: GET /users?cp=2&pp=20&se=6. Instead of saving 956 as the queryStr in the the view, it only saves the most recent character entered, instead of the entire content of the serch text.
This happens every time I type in multiple characters as a search query, and then make another call to the server using the unchanged this.searchQuery variable. If my initial search query is only a single character, it works just fine.
What am I doing wrong here? How can I emit the entirety of the text in the search bar, after any change, so that I can always save the whole search query, instead of the just the most recent change?
EDIT: I've add some more code below so the data flow is easier to follow:
Here is the template and script for the search component:
<template>
<div class="level-item">
<div class="field has-addons">
<div class="control">
<input class="input" type="text" placeholder="Address" v-model.lazy="searchQuery" v-on:input="(event) => this.$emit('queryChange', event)">
</div>
<div class="control">
<div class="button is-light" #click="clearInput">
<span class="icon is-small">
<i class="fa fa-times" style="color:#ffaaaa"></i>
</span>
</div>
</div>
<div class="control">
<button class="button is-info" #click="onSearch(searchQuery)">Search</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Search',
props: {onSearch: Function},
data () {
return {
searchQuery: ''
}
},
watch: {},
methods: {
clearInput () {
this.searchQuery = ''
}
}
}
</script>
the emitted queryChange event is caught and listened to in the vue page:
<Search :onSearch="onSearch" v-on:queryChange="updateSearchQuery"> and this triggers the updateSearchQuery function:
updateSearchQuery (event) {
this.searchQuery = event.data
console.log(event.data + ' || event.data')
console.log(this.searchQuery + ' || this.searchQuery')
}
Theoretically, the searchQuery data in my vue should be a copy of the searchQuery data in my component, which is itself merely a copy of whatever the user has input in the search bar.
Then when I make a call to the server I'm using the value in this.searchQuery in my vue:
onSearch (search) {
this.makeServerQuery(1, search)
},
onPaginate (page) {
this.makeServerQuery(page, this.searchQuery)
},
makeServerQuery (page = null, search = null) {
let queryStr = ''
if (page !== null) {
queryStr += '?cp=' + page + '&pp=' + this.perPage
}
if (this.searchQuery !== '') {
queryStr += '&se=' + this.searchQuery
} .....
The on onSearch(search) function is called whenever the search button is pressed. That seems to work fine, because when the button is pressed the entire searchQuery is passed, not just the last change.
An input event's data value appears to be the last typed character, and not the current value of the input. A simple fix is:
#input="$emit('queryChange', searchQuery)"
This works because the model will always be updated before the input event handler runs.
Here's a complete working component example:
<input
v-model="searchQuery"
type="text"
placeholder="Address"
#input="onInput"
/>
export default {
data() {
return { searchQuery: '' };
},
methods: {
onInput() {
console.log(this.searchQuery);
this.$emit('queryChange', this.searchQuery);
},
},
};

Ng-click and ng-change aren't being fired when using component and template angular 1.6

I am trying to fire a function when something is typed into the input. For some reason, it is not working when I use a template and component. Can anyone help me figure out why this is happening? I am new to Angular by the way.
Component
When the button is clicked or when something is typed into the input, the ng-click and ng-change functions should fire but they are not.
(function(angular) {
'use strict';
angular
.module('app')
.component('coinSearch', {
controller: CoinSearchController,
controllarAs: 'coin',
templateUrl: 'src/coinSearch.html'
});
function CoinSearchController(CryptoService) {
var coin = this;
var list = [];
coin.jank="something weird";
coin.savedCoins = [];
coin.searchedCoin = '';
function getCrypto() { //pulls data from API
CryptoService
.retrieve()
.then(function(response) {
coin.list = response;
});
}
coin.click = function() {
console.log('HELLOOO');
};
coin.showSearch = function() {
console.log('hello');
return coin.searchedCoin === '';
};
getCrypto();
}
})(angular);
Template
Template for the component above. There are some console.logs for testing purposes.
<div class="container">
<form class="search-form">
<button ng-click="coin.click()">{{coin.jank}} </button> //testing
<input
type="text"
class="search"
placeholder="Search Crypto"
ng-model="coin.searchedCoin"
ng-change="coin.showSearch()">
<ul class="suggestions">
<li ng-hide="coin.showSearch()" ng-repeat="coins in coin.list |
filter:coin.searchedCoin">
<span>{{coins.name}} ({{coins.symbol}})</span>
<span>${{coins.price_usd}}</span>
<span><button ng-
click="coin.addToList(coins);">Add</button></span>
</li>
</ul>
</form>
<coin-list></coin-list>
</div>
If you look at your controllerAs statement, you have it spelled as controllarAs. That would explain why nothing is listening in the template when you use coin.