Angularjs scope variable in nested http get - javascript

I have a problem accessing to a scope variable defined in a nested $http.get. This is my controller code (it's related to a partial $routeProvider)
//Content controller
app.controller('Content', ['$scope', '$routeParams', '$http', function($scope, $routeParams, $http) {
$http.get('wp-json/wp/v2/posts?slug=' + $routeParams.slug).success(function(res) {
$scope.post = res[0];
document.querySelector('title').innerHTML = res[0].title.rendered + ' | Going Solo ';
console.log(res);
$http.get('wp-json/wp/v2/posts?filter[artists]=' + $scope.post.pure_taxonomies.artists[0].slug + '&exclude=' + $scope.post.id).success(function(res) {
if (res.length > 1) {
$scope.related = res;
}
else {
$scope.related = res[0];
}
console.log(res);
});
}).error(function(res, status) {
if (status === 404) {
$scope.is404 = true;
document.querySelector('title').innerHTML = 'Page not found | Going Solo';
$scope.errorMessage = 'Error: ' + res[0].message;
}
});
}]);
Basically I want to retrieve all the songs related to the one that is showed in my content controller (the first $http.get). To link all the songs I use a custom taxonomy called “artists”. Of course I need this to be asynchronous so I do a new $http.get inside the first one. In this request I filter for the taxonomy slug of the current post ($scope.post.pure_taxonomies.artists[0].slug) plus I add a filter to exclude the post itself by adding its ID. This is working correctly by looking at the console log. It returns an array(1) for the first request and an array(2) for the second requests (with the correct data).
The problem is by the time I try to access in my partials to the second $http.get.
If I try this ng-repeat:
<div ng-repeat="songrel in related">
<div ng-bind-html="songrel.title.extended"></div>
</div>
Nothing is showed. It simple doesn't enter in this cycle, so I guess he doesn't recognize data.related. What am I missing?

It can't find data.related because there's no $scope.data variable declared. Try just related, i.e.
<div ng-repeat="songrel in related">
<div ng-bind-html="songrel.title.extended"></div>
</div>

Related

How can i search through a json api using angularjs/ionic

How can i check if the id that i get from the url is equal to any of the ids in the json api here is my code
.controller('PlayAod', ['$scope', '$http', '$state', '$stateParams', '$location', function ($scope, $http, $state, $stateParams) {
$scope.title = 'Sample Company';
$scope.apiusername = 'hoy';
$scope.apipassword = '076fdf74936ebae00e7949a80f61f985';
$scope.aodid = $stateParams.ahls;
$scope.stream = "";
$http.get('http://tvstartup.biz/mng-channel/vpanel/api/audios.php?user=' + $scope.apiusername + '&pass=' + $scope.apipassword).success(function (data) {
$scope.aodhls = data.audios;
if (data.audios.id == $scope.aodid) {
$scope.stream = date.audios.hls_stream;
}
});
$scope.play = function() {
window.cndStream.play($scope.stream);
}
$scope.pause = function() {
window.cndStream.pause();
}
}])
please help if you have any questions post in comments
As I have experience with your problem from another question, I will add context for other users that your $http request here returns an array of objects, and each of the objects has a property named 'id'.
Assuming that you are trying to track down the particular object within the array of which object.id == $scope.aodid, you can search over the array until the comparative proves true. One such example would be:
// Inside of $http.get success function
var audioList = data.audios;
for(var audio of audioList) {
if(audio.id == $scope.aodid) {
// Oh, joy this is the one I want!
// Perform some magic...
// Or maybe in your case...
$scope.stream = audio.hls_stream;
// Dont forget to stop searching...
break;
}
}
A potentially better solution would be to provide the ID in the query such that you only get back what you want and don't have to iterate a list, however I am infering a bit as to what you are ultimately trying to do.

Ng-repeat not updating array

As the title says, I have a problem with reference switching.
My html:
div ng-repeat="data in parseurl">
{{data.url}}
</div>
In my JS code, I'm trying to do two things. The first step is to grab the data off a server and put it into an array (called allsongs). Afterwards, I parse the data and put it into another array (parseurl).
var app = angular.module("write", []);
app.controller("Ctrl", ['$scope', '$http', function($scope, $http){
$scope.allsongs = [];
$scope.parseurl = [];
$scope.getstuff = function(){
$http.get("my link here").then(function(response){
$scope.allsongs = response.data;
}); //step one -> this works!
$scope.parser(); //step two
};
$scope.parser = function()
{
for(i=0;i<$scope.allsongs.length;i++) {
for(var key in $scope.allsongs[i]) {
var value = $scope.allsongs[i][key];
var object = {Url : value.Url}; //this does contain the correct info I want
$scope.parseurl.push(object);
}
$scope.getstuff();
}]);
So what is happening is that, if I ng-repeat on allsongs, then I do get a bunch of un-parsed urls. But if I ng-repeat on parseurl, then I get nothing. Obviously the reference isn't changing, but how can I do it?
$scope.parser() needs to be called after the data was recived. Put it into your promise callback function like in the following example. Please note that $http is an asynchronous function. In that way $scope.parser() was executed before your request has been finished.
$scope.getstuff = function(){
$http.get("my link here").then(function(response){
$scope.allsongs = response.data;
$scope.parser();
});
};

Accessing Fetched Resources From a Controller?

In my AngularJS controller I'm trying to do something relatively simple: I'm trying to populate a <select> element dynamically in the controller. To do so I need to wait for my localized UI text data to be loaded and data from my server to be loaded and this is causing a problem for me.
What my HTML Looks like:
<select
data-ng-model="group"
data-ng-options="options.value as options.label for options in userGroups">
<option>--</option>
</select>
Then my controller is actually implementing a base controller "class" which allows me to share logic between controllers:
// exampleController.js
myModule.controller('exampleController',
['$scope', '$timeout', '$routeParams', '$controller',
function ($scope, $timeout, $routeParams, $controller) {
// Instantiate the base controller class and set it's scope
// to this controller's scope. This is how the base and child
// controllers will share data and communicate.
var base = $controller('baseController', { $scope: $scope });
}]);
And here is a relevant snippet of the baseController:
// baseController.js
$scope.getDataFromUrl = function (url, cache, successFunction) {
$http.get(url, { cache: cache })
.success(function (data) {
if (!handleErrorInData(data))
{
successFunction(data);
}
});
};
$scope.getDataFromUrl(uiTextResourceUrl, true, function (data) {
$scope.uiText = data;
});
So baseController fetches the text resources when it loads and sets it to the scope when it's finished retrieving the data. exampleController on the other hand will fetch other data from the server via the getDataFromUrl() function defined in baseController like so:
$scope.getDataFromUrl(dataUrl, false, function (data) {
// Do stuff with the returned data...
};
My issue is coming from this part of the exampleController code where I populate the data of the <select> element from earlier:
// exampleController.js (Continued...)
$scope.getDataFromUrl(userGroupsUrl, false, function (data) {
initSelectDropdown(data);
});
var initSelectDropdown = function (data) {
var userGroups = [];
// Parse data retrieved from the server and populate the <select> bound data
// array with it
var index;
for (index = 0; index < data.length; ++index)
{
var newGroup = {
value: data[index],
label: data[index]
};
// One of the data entries will have a value of "", this group needs its
// label to be set to the localized string "No Group"
if (newGroup.value === "")
{
newGroup.label = '<' + $scope.uiText['NoGroup.Text'] + '>';
}
userGroups.push(newGroup);
}
// Set local userGroups to scope
$scope.userGroups = userGroups;
};
The problem I'm having is here in the initSelectDropdown() function. I need to have both the data from the server and the uiText resource data from the server, particularly the line newGroup.label = '<' + $scope.uiText['NoGroup.Text'] + '>'; where the data is being transformed in a way that is dependant on localized resources being loaded. I researched the issue and saw that using $q.all() might be a solution but unfortunately in my case there is no way for me to call $q.all() because the two calls to fetch data are being made from different functions in different controllers (data being requested from child controller and resources being requested from base controller).
In the view it's easy to fix this because if I bind an element to $scope.uiText['SomeText.Text'] then it doesn't care if SomeText.Text is undefined at first and when it is eventually populated the UI will automatically pick up on the change.
How can I make this work? Is it possible to achieve something like how binding works in the view?
For sharing code angular provides services/factory, you don't need to use base controller.
Define a factory class and add two methods, one to fetch your server data and other to fetch uiText data. these methods will return promises.
Now in your controller you can use $q.all() passing the two promises that will be resolved when ajax call is complete.
Hope it makes sense ?

Variable caching in AngularJS (factory)

I have the following controller in my application, but there is some strange behaviour that I cannot explain. I've numbered two of the lines to help with the description, they don't both exist at the same time in the live code.
var app = angular.module('movieListings', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ng']);
var cachedMovieList = [];
//Controller for movie list
app.controller('MovieListController', ['$http', function($http){
var mlc = this; //needed for the $http request
this.movies = cachedMovieList;
this.loaded = false;
this.error = false;
if(this.movies.length == 0) {
console.log("Grabbing new movie list from DB");
$http.get('data/movies.json').success(function(data){
mlc.movies = data;
mlc.loaded = true;
cachedMovieList = data; //(1)
}).error(function(data){
mlc.error = true;
});
cachedMovieList = this.movies; //(2)
} else {
this.loaded = true;
}
}]);
With the code as above with line (1) present and line (2) not present, I am able to cache the result so that when I flick between pages I don't need to constantly re-get the data.
However if I remove line (1) and insert line (2), the variable "cachedMovieList" is never populated. I would expect it to be based on the fact that "mlc.movies" was assigned to... but I cannot understand why this is the case?
Any advice welcome.
Implement a factory that retrieves the data. Use angular.copy to preserve the array reference when the data returns from the $http call.
var app = angular.module('movieListings', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ng']);
app.factory('movies', function($http) {
var movies = {
data: [],
loaded: false,
error: false
};
$http.get('data/movies.json').success(function(data){
angular.copy(data, movies.data);
movies.loaded = true;
}).error(function(data){
movies.error = true;
});
return movies;
});
Inject the factory into your controller:
//Controller for movie list
app.controller('MovieListController', ['$scope','movies', function($scope, movies){
this.movies = movies;
}]);
Factories (like services) are singletons. They are initialized once, and cached for the entire lifetime of the SPA.
Use the controller in the view:
<div ng-controller="MovieListController as ctrl">
<div ng-show="!ctrl.movies.loaded"> Loading... </div>
<ul>
<li ng-repeat="movie in ctrl.movies.data">
{{ movie.name }}
</li>
</ul>
</div>
If I've understood this correct, you're entering the if condition only when this.movies.length == 0. In such a case, this.movies will be null, so cachedMovieList would get populated with a null value.
Because (2) probably gets executed first before the $http.get() request is finished. $http.get() is an AJAX request.
If you want to cache, you might want to use $cacheFactory instead :)
I believe you are mistaking the live updation of values that happens in view to live updation that would happen with variable assignments. Your line 2 will set cachedMovieList to [] initially. I believe that is quite obvious. But you think that since callback updates this.movies that change would cascade to cachedMovieList. That won't happen as you are re-assigning the mlc.movies variable that means it refer to new variable instead of modifying existing value.
If you really want to make you logic work, please update mlc.movies variables like following
mlc.length = 0 // Empty the array
mlc.push.apply(mlc, data);
Please check following answer for more information
How do I empty an array in JavaScript?

Angular Factory Variables Won't Update - Instance gets old values

I have a resource factory that builds objects for accessing our API. I use an environment variable to determine the base part of the URL - whether or not to include 'account/id' path segments when the admin user is 'engaging' a client account.
The sessionStorage item that holds the 'engagedAsId' doesn't get read, though for instances created after engaging an account. It requires a full reload of the app to pick up that change. Here is the factory code:
myapp.factory('ResourceSvcFactory',
['$rootScope', '$resource',
function ($rootScope, $resource) {
function ResourceSvcFactory (endpoint) {
// vvv problem is here vvv
var accountId = sessionStorage.getItem('engagedAsId');
var apiPath = (accountId != null)
? '/api/account/' + accountId + endpoint
: '/api' + endpoint;
var Resource = $resource(apiPath+':id/',{
// default params
id:''
},{
// custom actions
update: {method: 'PUT'}
});
return Resource;
}
return ResourceSvcFactory;
}]);
myapp.factory('AssetsResource', ['ResourceSvcFactory', function (ResourceSvcFactory) {
var endpoint = '/assets/';
var Resource = ResourceSvcFactory(endpoint);
return Resource;
}]);
I implement this in my Controller like this:
myapp.controller('AssetGroupListCtrl', [ 'AssetgroupsResource', function (AssetgroupsResource) {
var AssetGroups = AssetgroupsResource;
// ... rest of controller
}]);
When i run this it works fine. But, if i change the engaged status in the sessionStorage without a full reload, the instance in the controller does not pick up the new path.
Is there a way to 'refresh' the instance? ...automatically?
After hours of research, it appears that the fundamental flaw in what I'm trying to do in the question is this: I'm trying to use a 'singleton' as a 'class'. from the docs:
Note: All services in Angular are singletons. That means that the injector uses each recipe at most once to create the object. The injector then caches the reference for all future needs.
http://docs.angularjs.org/guide/providers
My work around was to create the $resource inside a method of a returned object. Here is an example:
MyApp.factory('AssetgroupsResource',
['$rootScope', '$resource',
function ($rootScope, $resource) {
return {
init: function () {
var accountId = sessionStorage.getItem('engagedAsId');
var apiPath = (accountId != null)
? '/api/account/' + accountId + endpoint
: '/api' + endpoint;
// default params
id:''
},{
// custom actions
});
return Resource;
}
}
}]);
This made it possible to build the object at the right time in the controller:
MyApp.controller('AssetGroupListCtrl', ['Assetgroups', function (Assetgroups) {
var Assetgroups = AssetgroupsResource.init();
// now I can use angular's $resource interface
}]);
Hope this helps someone. (or you'll tell me how this all could've been done in 3 lines!)
You can always call $scope.$apply(); to force an angular tick.
See a nice tutorial here: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
I think $resource uses promise which might be an issue depending on how you implement your factory in your controller.
$scope.$apply() can return an error if misused. A better way to make sure angular ticks is $rootScope.$$phase || $rootScope.$apply();.

Categories