state wont display data on reload Ui-Router and angularfire - angular-ui-router

I have an SPA 'blog' using ui-router and using firebase as a backend with Angular Fire. The blog page calls the array no problem and loads correctly. When i select an individual post it loads with the $stateParams({id:post.$id}) from the array pulled from firebase via Posts.
however when i refresh that page, the unique post is lost.
code as follows: Router =
.state(homeState = {
name: 'root.home.post',
url: '/{{postId}}',
views: {
'main#': {
templateUrl: 'views/post/post.html'
}
}
})
post.js =
var id = $stateParams.postId; // console.logs correctly even after refresh
var ref = Posts; //console.logts $resolved: false after refresh though data from main page still in the array
var post = ref.$getRecord(id); //console.logs as null after refresh
$scope.post = post;
Not sure if thats enough to go on, but i have struggled with this for a few hours now and want to sleep. Cheers.

fixed my own problem (was two fold)
Updated post.js to include the update of the $scope after .$loaded as it was loading after the rest of the page was painted:
var id = $stateParams.postId;
console.log(id);
var ref = Posts.$loaded().then(function(ref){
var post = ref.$getRecord(id);
$scope.post = post;
});
I also updated the routes to remove {} around the parameter:
.state(postState = {
name: 'root.post',
url: '/post/{postId}',
views: {
'main#': {
templateUrl: 'views/post/post.html'
}
},
})

Related

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

Service worker not creating networkFirst Cache

My Service Worker:
importScripts('https://storage.googleapis.com/workbox-
cdn/releases/3.0.0/workbox-sw.js');
//Use Workbox Precache for our static Assets
workbox.precaching.precacheAndRoute([]);
console.log('this is my custom service worker');
//Create articles Cache from online resources
const onlineResources = workbox.strategies.networkFirst({
cacheName: 'articles-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
}),
],
});
workbox.routing.registerRoute('https://newsapi.org/(.*)', args => {
return onlineResources.handle(args);
});
The precache cache works but the onlineResources Cache is never created.
A look at my file structure:
So I don't think scope is an issue even though I cant see clients in my service worker on Chrome dev tools.
Lastly here is my app.js file:
//main populates main tags in indexpage
const main = document.querySelector('main');
//this populates the source dropdown menu with sources
const sourceSelector = document.querySelector('#sourceSelector');
//set default source so page loads this
const defaultSource = 'bbc-news';
//on window load call update news and when update
window.addEventListener('load', async e => {
updateNews();
await updateSources();
sourceSelector.value = defaultSource;
//when sourceSelector is changed update the news with the new source
sourceSelector.addEventListener('change',e =>{
updateNews(e.target.value);
});
//checks for serviceWorker in browser
if('serviceWorker'in navigator){
try{
//if there is register it from a path
navigator.serviceWorker.register('/sw.js');
console.log('registered!');
} catch(error){
console.log('no register!',error);
}
}
});
async function updateNews(source= defaultSource){
//response awaits a fetch of the news API call
const res = await fetch(`https://newsapi.org/v2/top-headlines?sources=${source}&apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//fill the html with the retrieved json articles
main.innerHTML = json.articles.map(createArticle).join('\n');
}
//Update the news source
async function updateSources(){
const res = await fetch(`https://newsapi.org/v2/sources?apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//Whatever source the sourceSelector picks gets mapped and we take the id of it - It is used with updateNews();
sourceSelector.innerHTML = json.sources
.map(src=>`<option value="${src.id}">${src.name}</option>`).join('\n');
}
function createArticle(article){
return ` <div class="article">
<a href="${article.url}">
<h2>${article.title}</h2>
<img src="${article.urlToImage}">
<p>${article.description}</p>
</a>
</div>
`;
}
App.js plugs into newsAPI and outputs the JSON to the pages HTML.
When you register the route you seem to be trying to use a regex as a string. I think it is literally interpreting the route as a string that includes .*. Instead try the regex /^https:\/\/newsapi\.org/ which per the docs will match from the beginning of the url.

Updating vuejs component with ajax call from the parent

New to VueJs. I'm wondering how/where would I make an Ajax call to pull data dynamically down to populate the following Vue table?
https://jsfiddle.net/yyx990803/xkkbfL3L/?utm_source=website&utm_medium=embed&utm_campaign=xkkbfL3L
I've (roughly) modified the example above as follows:
var demo = new Vue({
el: '#demo',
data: {
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: []
},
methods: {
fetchUsers: function() {
...
// ajax call using axiom, fetches data into gridData like this:
axios.get('http://localhost/url')
.then(function(response) {
this.gridData = response.data;
})
.catch(function(error) { console.log("error"); })
...
}
},
created: function() {
this.fetchUsers();
}
})
I'm trying to incorporate the ajax pieces from here:
https://jsfiddle.net/chrisvfritz/aomd3y9n/
I've added the fetchUser method which makes the ajax call to pull the data down. I'm able to pull down my data and print it to the console using both fetch and axiom, so I know that part works.
However, my data never appears or updates. The table loads blank. I think it has something to do with me putting the method and created hook on the Vue model object (demo), rather than on the component itself. But I'm not quite sure how to modify the example to resolve it, as the example passes the data in from the parent.
Can someone give me some guidance?
You problem is right over here:
.then(function(response) {
this.gridData = response.data;
})
Within your anonymous function within your then you don't have the context you expect. The most simple solution is adding a .bind(this) to the method.
.then(function(response) {
this.gridData = response.data;
}.bind(this))
By adding it your method body will be aware of the outer context and you can access your components data.

angular - invoke() on exising element undefined

I've a div (id: dest) in which content is loaded via ajax using query.
I need to compile with angular the content of that div.
Here is the content loaded via ajax:
{{myvar}}
I tried to do:
var app = angular.module("myapp", []);
app.controller("myctrl", function($scope){
$scope.myvar = "hello world!";
});
$("#dest").attr("data-ng-app", "myapp");
$("#dest").attr("data-ng-controller", "myctrl");
console.log(angular.element($("#dest")).size()); //always returns '1'
angular.element($("#dest")).injector().invoke(function($compile) {
var scope = angular.element($("#dest")).scope();
$compile($("#dest"))(scope);
});
I'm sure that $("#dest") exists in dom when that code is executed but
angular.element($("#dest")).injector() is always undefined.
I also tried to wait 15 seconds:
setTimeout(function(){
angular.element($("#dest")).injector().invoke(function($compile) {
var scope = angular.element($("#dest")).scope();
$compile($("#dest"))(scope);
});
}, 15000);
But the problem remains.
PS.
I cannot interact directly with ajax request or response and the div (with id dest), exists at page loading.
Have you tried wrapping that code with $(document).ready() or angular's equivalent:
angular.element(document).ready(function () {
angular.element($("#dest")).injector().invoke(function($compile) {
var scope = angular.element($("#dest")).scope();
$compile($("#dest"))(scope);
});
});
Working jsfiddle.
Have you tried this :
angular.element($("#dest")).injector().invoke(['$compile', function($compile) {
var scope = angular.element($("#dest")).scope();
$compile($("#dest"))(scope);
}]);

How to deal with async requests in Marionette?

I am trying to fill in an ItemView in Marionette with the combined results of 2 API requests.
this.standings = App.request('collection:currentStandings');
this.userInfo = App.request('model:userInfo');
this.standings.each(function(s) {
if (s.currentUser) {
s.set('alias', this.userInfo.alias);
s.set('imageURL', this.userInfo.imageURL);
}
});
userInfoView = new LeagueBar.UserInfo({ collection: this.standings });
The problem is, the combination never happens because the requests have not been fulfilled before I try to combine them.
I know I probably need to add a promise for each request, but I haven't been able to find a clean way to do it. I could make 'collection:currentStandings' and 'model:userInfo' return promises, however, they are currently used in many other parts of the code, so I would have to go back and add .then()s and .done()s all over the code base where they weren't required before.
Any ideas or suggestions?
EDIT:
I have currently solved this in a less-than-ideal way: I created a template/view for the alias and a template/view for the imageURL and kept the template/view for the standings info. This doesn't seem like the best way and I'm interested to know the right way to solve this problem.
here are the two requests I am trying to combine:
Models.CurrentStandings = App.Collection.extend({
model: Models.PlayerStandings,
url: function() { return 'leagues/' + App.state.currentLeague + '/standings'; },
parse: function(standings) {
return _.map(standings, function(s) {
if (s.memberId == App.user.id)
s.currentUser = true;
return s;
});
}
});
App.reqres.setHandler('collection:currentStandings', function() {
weekStandings = new Models.CurrentStandings();
weekStandings.fetch({ success: function(data){ console.log(data); }});
return weekStandings;
});
Models.UserInfo = App.Model.extend({
url: 'users/me'
});
App.reqres.setHandler('model:userInfo', function(options) {
myuser = new Models.UserInfo();
myuser.fetch(options);
return myuser;
});
There are 2 solutions which based on your dependencies among views can be selected:
You can create views which are handling 'change' event of Models.UserInfo and when the data is ready (Change/Reset event raised) re-render the content. It is probably your solution.
If you are looking for a solution which should not create instance of LeageBar.UserInfo until both Models.CurrentStanding and Models.UserInfo are ready, you have to return the result of fetch function, so you may remove calling fetch from setHandlers and use them as following:
this.standings = App.request('collection:currentStandings');
this.userInfo = App.request('model:userInfo');
var that=this;
that.standings.fetch().done(function(){
that.userInfo.fetch().done(function(){
that.standings.each(function(s) {
if (s.currentUser) {
//....
}
});
userInfoView = new LeagueBar.UserInfo({ collection: that.standings });
});

Resources