How is Vue v3 able to use undefined variables? - javascript

We are creating an webapp to watch for analytical changes and update them in real time, (this shouldn't be important just figured I'd let you know.)
On Vue.js's offical website https://v3.vuejs.org/guide/computed.html#computed-caching-vs-methods they have an example of using watchers.
Code:
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</div>
<!-- Since there is already a rich ecosystem of ajax libraries -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to use what you're familiar with. -->
<script src="https://cdn.jsdelivr.net/npm/axios#0.12.0/dist/axios.min.js"></script>
<script>
const watchExampleVM = Vue.createApp({
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// whenever question changes, this function will run
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
In the code you can see the variables Old Question and New Question, so the question is how does it know the values of those 2 when they are never defined?
We did try and import this into our project just to see if the website itself was giving it the values, however, after seeing that it worked (with minimal changes) we did not believe this was the case.
Any help would be much appreciated.
-Shark

So they are not defined because these are not variables but instead parameters.
The watcher always receives 2 parameters. The old value and the new value.
When you type into the input <input v-model="question" /> which is bound with v-model to question the value of question changes, this will trigger the watcher with the new value and the old value to which you can run logic on. You could use a watcher on something like fullName and look for spaces and set this.firstName and this.lastName or like the example is doing, looking for the value of a question mark to trigger a method.

Related

How to insert tabledata into table with vue.js?

I'm working in a project with vue-cli and they want me to generate a listlike view (pun intended) onto data from the database.
I've never really worked with vue. 4 months ago I was given some time to find my way into it, but then I had to work on our lumen-driven backend api and so I forgot most of the stuff. And besides that, I really find vue utterly confusing.
So I need to do this step by step since I dont have loads of time at hand to throughly learn the framework before actually producing usable results.
I have the following template:
<template>
<div>
<h1>myList</h1>
<table id="testTable"></table>
<button class="button" v-on:click='fetchList'>myButton</button>
</div>
</template>
<script>
import store from "../store/store";
import { USER_FETCHLIST } from "../store/actions/user";
export default {
data () {
return {
}
},
methods: {
fetchList: function(){
var test = store.dispatch(USER_FETCHLIST).then((res)=> {
console.log(res)
document.getElementById("testTable").InnerHTML = res
})
}
}
}
</script>
<style>
</style>
The fetchList() successfully fetches the data from the DB. But I cant insert anything into my table element, at least not this "javascript-way" which I tried in the above code.
For example, when I try to input this string: <tr><td>1</td><td>2</td></tr> into the table element, nothing happens.
I learnt about v-bind and all this stuff, but I really don't know how to implement it here.
I also must emphasize that I absolutely want to avoid building this component from subcomponents.
The "inheritance" in vue.js is pretty different from the usual inheritance in programming and I really tried to get my head around it, but I just cant make anything useful with it in my relatively short timeframe.
So I need to have all the JS and html in place in this component.
Can anyone give me a hand in propery binding the prefabricated HTML-String to the table element?
Thank you!

Need for Binding Selected Bits of External Data in $data

I'm a fan of Vue which a try to use on some occasions. Anyway, there is something I always found not so handy with it: reactivity lies within $data. Well not always, as external data can be tracked by Vue, as in computed properties, in templates… But I found this way uncomfortable and not always consistent (see another question about it, here Reactivity on Variables Not Associated With Data, Computed, etc). So my decision now is use $data as the main source of reactivity and stop trying to find other ways.
However, reactivity within $data poses me a problem in what is a common case for me: many pieces of data here and there in other imported objects. This makes even more sense as I consider Vue as the View end not the business logic. Those imported objects are sometimes complex and within Vue components, I found no way to cherry pick pieces of information and kind of ask Vue to bind to them. The only way was to declare entire objects in the $data section which makes tracking very heavy: loads of setters/getters when only one would be enough in a simple component, for example.
So I designed a class called 'Reactor' whose instances role is to install getter/setters on any piece data of my wish in a complex object (or more than one). Those instances are imported into Vue components and then $watchers of Reactor instances have properties which can contain as many functions as I wish which are called when pieces of data are altered through the Reactor. To make things simple by default is filled with the same property name as the data it bounds to. This precisely those function which will update $data when external data change.
class Reactor {
constructor() {
this.$watchers = {};
}
addProperty(originalObject, keyString, aliasKeyString) {
if(aliasKeyString === undefined) {
aliasKeyString = keyString;
}
if(this[aliasKeyString] !== undefined || originalObject[keyString] === undefined) {
const errorMessage = `Reactor: cannot add property '${aliasKeyString}'!`;
console.error(errorMessage);
throw errorMessage;
}
this.$watchers[aliasKeyString] = [];
Object.defineProperty(this, aliasKeyString, {
set(newValue) {
const oldValue = originalObject[keyString];
originalObject[keyString] = newValue;
this.$watchers[aliasKeyString].forEach((f) => {
if(typeof f === "function") {
f(newValue, oldValue, aliasKeyString);
}
});
},
get() {
return originalObject[keyString];
},
});
}
}
An example can be seen in the codepen here: https://codepen.io/Djee/pen/gyVZMG
So it's sort of an 'inverted' Vue which allows updating $data on external conditions.
This pattern also helped me resolve a case which was rather difficult before: have a double-bind on an input with a filter in-between which will set the input and its attached external value straight upon #change event only. This can be seen in the same codepen given above.
I was a little surprised to have found nothing taking this in charge in Vue itself. Did I miss something obvious? This is mainly the purpose of this somewhat long introduction. I had no time to check whether Vuex would solve this nicely.
Thanks for any comments as well.

Using data from Observable Angular 2

First, I'd like to thank the community for all the support learners like me get while working with new technologies.
I've been using Angular for a while now, and there's something I still don't quite understand nor have I seen asked elsewhere.
Supposing I have a service that returns an Observable with the data I need, how should I use this data properly, in terms of performance?
I know I can use the async pipe and avoid having to sub/unsub, but this only happens in the template. What if I needed to use the same data in the component as well? Wouldn't subscribing again (from the template with the async pipe and from the component with .subscribe())?
How do I keep the observable up to date? For example, I have a table displaying data from an API. If I click on the 2nd page (pagination), I'd like to make another call and have the observable updated.
I'm sorry if this has been asked before, I personally couldn't find if on Stackoverflow. Thanks for your attention!
If you need the data in the component as well, you can just subscribe to it. BUT maybe you should not (see below)...
it's there that you use the operators, you can combine observables to define a custom data flow:
foo$: Observable < Foo[] > ;
randomClickEvent = new Subject < clickEvent > ();
ngOnInit() {
let initialFetch = this.fooService.getData().share()
this.foo$ = Observable.merge(
initialFetch, // need the initial data
initialFetch.flatMap(foos => {
this.randomClickEvent.switchMap(() => { //listen to click events
return this.fooService.getMore().map((moreFoos: Foo[]) => { //load more foos
return foos.concat(...moreFoos) //initial foos values + new ones
})
})
})
);
}
<span *ngFor="let foo of (foo$|async)">{{foo.name}}</span>
<button (click)="randomClickEvent.next($event)">Load More foos !</button>
Most of people just use simple operators like map(),do(), etc and manage their subscription imperatively, but it is usually better to not subscribe, so you avoid many side effects and some "Ooops I forgot to unsubscribe here". usually you can do everything you need without subscribing.
Observables exist to describe a data flow, nothing more, nothing less. It's a paradigm of functional programming: you don't define how things are done, but what they are. Here, this.foo$ is a combination of the initial fooService.getData() and every fooService.fetchMore() that may occur.

Why is Angular 2's template not updating from calls outside an angular zone?

I thought I would create a very simple login form with component-bound username and password properties that would run through the following steps:
Submit credentials with a fetch() call
THEN obtain the Response result object's JSON content
THEN check that content for the result of the server-side check
If the credentials were wrong, it would change a component property that would tell Angular to apply a class to the username and password inputs to make them red temporarily (using setTimeout to change that back)
The problem I ran into is that Angular would not correctly apply the class, and I wasn't sure why. I decided to create a simplified test project to narrow down the problem and ended up with the inclusion of system-polyfills/when.js being cause.
This code goes through 1, 2, then 3, and sets that both in the component property and outputs it to the debug console. Angular correctly renders the component property unless system-polyfill is included, in which case it will stop at 1 and never change it to 2 or 3, even though the console shows that the property is in fact changed:
export class App {
private stateIndicator:string = "0";
public showState(state:string) {
console.log('State: ' + state);
this.stateIndicator = state;
}
public showFetchProblem() {
this.showState('1')
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.showState('3')
});
});
}
}
I created the following Plunker to demonstrate this behaviour: http://plnkr.co/edit/GR9U9fTctmkSGsPTySAI?p=preview
And yeah, the obvious solutions are:
Don't manually include system-polyfills, or
Manually include a differeny Promise polyfill before SystemJS if you need it
But I'm still curious why this is happening, and hopefully somebody can shed some light on this (and possibly help to resolve the base issue).
Edit: The original title of this was Why is Angular 2's template rendering misbehaving when using system-polyfills (when.js). Thanks to Thierry and Günter for contributing and pointing out that Angular 2's use of zones is at play here. For anybody who comes across this in the future, here are two excellent articles that explain zones in further detail and will enhance your understanding of this scenario should you run into it:
http://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html
http://blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html
Promise polyfill provides a custom implementation of Promise that seems to be executed outside the scope of Angular2 (i.e. not in a zone).
If you execute your code explicitly within a zone managed by Angular2 (with the NgZone class), it works when the system-polyfill.js file is included.
Here is a sample:
constructor(private zone:NgZone) {}
public showFetchProblem() {
this.showState('1')
this.zone.run(() => {
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.showState('3')
});
});
});
}
See the corresponding plunkr: http://plnkr.co/edit/EJgZKWVx6FURrelMEzN0?p=preview.
update
Not sure about the explanation anymore but the workaround works. But I think there is some other underlying issue.
See also https://github.com/angular/angular/issues/7792#issuecomment-211217775
original
I assume the problem is caused by fetch not being patched by Angulars zone
To work around make the code execute inside Angulars zone manually
export class App {
constructor(private zone:NgZone) {}
private stateIndicator:string = "0";
public showState(state:string) {
console.log('State: ' + state);
this.stateIndicator = state;
}
public showFetchProblem() {
this.showState('1')
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.zone.run(() => this.showState('3'));
});
});
}
}
Plunker example

How to use Session.setDefault() with external data inside helper

I use Meteor 1.2.1. I am currently developing a Course Catalog Web App.
I'm trying to create a filtering feature just like Angular Filters. It should let the user specify his needs and then the course list refreshes automatically as he changes the filters.
This is my code:
app.html
<!-- ********** -->
<!-- courseList -->
<!-- ********** -->
<template name="courseList">
<div class="list-header">
<div>Course Name</div>
<div>Description</div>
<div>Category</div>
<div>Modality</div>
<div>Unit</div>
</div>
<div class="list-body">
{{ #each course }}
{{ > courseRow}}
<hr />
{{ /each }}
</div>
</template>
app.js
Template.courseList.helpers({
'course': function(){
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
}
});
So when I run this, I get an empty set for the course helper above, even though there's some dummy data present on the database.
It's seems to me that the issue is with Session.setDefault(). When I console.log the Session.course variable right after the find, I get an empty array [], maybe because there was no time to get the data from the server (or maybe not, because I'm developing with autopublish for now, I don't really know).
After I apply some of the filters (code not shown here), everything goes back to normal. This initialization only is the problem.
I've tried to call Session.set() inside Template.courses.rendered(), Template.courses.created(), Template.courses.onRendered(), Template.courses.onCreated() (yes, I was kinda desperate) but none of them worked.
Can someone please advise on that issue? Maybe I'm not trying the correct Meteor aproach, as I am a Meteor beginner.
Thanks!
I think you try to use template subsciption. so you can send some terms for publication. example;
Template.foo.onCreated(function() {
this.name = new reactiveVar("");
this.autorun( () => {
const terms = {
name: this.name.get()
};
this.subscribe("Posts_list", terms);
});
});
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
There are two things here. First thing is that your helper will run whenever either the result of Courses.find().fetch() or Session.get('course') changes, since both of them are reactive data sources. To avoid this, you can do
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
If you want to set the session variable so that you can use it somewhere else, then you can use Session.set instead of Session.setDefault, because Session.setDefault will only assign value only is the session variable is not already available. But I don't know why you want to do that, since you can use Posts.find().fetch() where ever required instead of session variable. Both are reactive.
Template.foo.helpers({
lists: function() {
Session.set('course', Courses.find().fetch());
return Session.get('course');
}
});
If you want to assign the course session variable only for the first time when you get non-empty data then you might want to do something like this.
Template.foo.helpers({
lists: function() {
if (Courses.find().count() > 0) {
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
} else {
return [];
}
});
If you can tell why you need the session variable, we might think of a different approach to solve the problem.
Hope it helps.

Categories