Angular API calls and promises - javascript

I'm working on an angular app and having a difficult time with one seemly simple operation. Basically, I'm making a call to the soundcloud api, grabbing my tracks, then looping through those tracks and grabbing the iframe embed object, injecting that into the tracks object then sending that whole thing as a promise to be resolved and stored in a $scope.soundcloud object. Just fyi, the second SC call is necessary to generate the widget html. I wish it wasn't but it is hah.
This all happends as it should and i can see the object in $scope. My template picks up the initial data (main track data), and console.logging the object shows the track and embed data, but the template NEVER sees the embed data.
So, fundamentally, How do I get my template to see the embed data, so i can use it with a directive or ng-bind-html? Below is all my code, please ask if you need any more information! Thank you all very much.
HTML
<div class="track" ng-repeat="track in soundcloud.tracks">
<div class="front">
<img src="app/img/loading.gif" />
</div>
<div class="back" ng-bind-html="{{track.oembed}}">
</div>
</div>
Angular Service
getTracks: function(){
var deferred = $q.defer();
var promise = deferred.promise;
SC.get("/me/tracks", function(tracks){
$.each(tracks, function(k, v){
if(v.sharing != 'private'){
SC.oEmbed(v.uri, function(oembed){
v.oembed = $sce.trustAsHtml(oembed.html);
});
} else {
v.oembed = null;
}
});
deferred.resolve(tracks);
});
return $q.all({tracks: promise});
}
Angular Controller
.controller("GridCtrl", ['$scope', 'Soundcloud', function($scope, Soundcloud){
// Init the Soundcloud SDK config
Soundcloud.initialize();
// Get the tracks from soundcloud
Soundcloud.getTracks().then(function success(results){
// Store tracks in the $scope
$scope.soundcloud = results;
console.log(results);
});
}]);

Try creating a directive like this:
app.module('yourModule').directive('embedTrack', function() {
return function(scope, elem, attr) {
elem.replaceWith(scope.track.oembed);
};
});
You then use it like this:
<div class="track" ng-repeat="track in soundcloud.tracks">
<div class="front">
<img src="app/img/loading.gif" />
</div>
<div class="back">
<div embed-track></div>
</div>
</div>
In case you want to pass it as an attribute to the directive, you need to use attr.$observe to make sure you get the value after the interpolation.
<div embed-track={{ track.oembed }}></div>
The directive would then be:
app.module('yourModule').directive('embedTrack', function() {
return function(scope, elem, attr) {
attr.$observe('embedTrack', function(value) {
elem.replaceWith(value);
});
};
});

Related

Evaluate or call directive on click in angular

I am trying to load an image from json and if image is not available, then show firstname and lastname.
I have controller like this:
app.controller('task1Controller',['$scope', 'taskFactory', '$state', 'imageTestService', function($scope, taskFactory, $state, imageTestService){
$scope.taskData = {};
$scope.current = 0;
taskFactory.get().then(function(response){
$scope.jsonData = response.data.data.resultCareGivers;
});
$scope.back = function(){
$scope.current = ($scope.current !== 0 ? $scope.current - 1 : 0);
};
$scope.next = function(){
$scope.current = ($scope.current !== $scope.jsonData.length-1 ? $scope.current + 1 : $scope.jsonData.length-1);
};
}]);
and a Directive to verify image loading:
app.directive('imageTestDirective', function($http){
return {
restrict: 'A',
link: function($scope, elem, attrs){
attrs.$observe('ngSrc', function(ngSrc){
if(ngSrc != null && ngSrc != ""){
$http.get(ngSrc).then(function(success){
$scope.noImage = false;
console.log('success loading image', success);
}, function(error){
$scope.noImage = true;
console.log('error loading image', error);
});
}
});
}
};
});
Html with attribute directive and next and back button to cycle through json:
<img image-test-directive ng-show="noImage === false" ng-src="{{jsonData[current].profilepic}}" alt=""/>
<div ng-if="noImage">
<div>{{jsonData[current].firstName.charAt(1)+jsonData[current].lastName.charAt(1)}}</div>
</div>
<div class="col-xs-12">
<div class="col-xs-2">
<button type="button" ng-click="back()">Back</button>
</div>
<div class="col-xs-6" style="text-align: right">
<button type="button" ng-click="next()">Next</button>
</div>
</div>
The problem is the directive works on page load and the image is loaded properly, but I when I navigate through json object to view details, the directive is not evaluated (I mean when there no image inside json, it should show firstName+lastName)
How do I achieve it?
I think you wrote the logic too complicated for this use case.
In basic words JSON you get from Server contains some data with/without image URL or with broken image URL.
So instead to make HTML to be complicated, just embed your JSON in service and bring new JSON to your controller.
for example:
app.service('SomeService',['$q', /* ... */ function ($q /* ... */) {
function MyNewJSON(origJSON){
// check here if origJSON contains wrong image url
// and set new JSON key hasImage = true or false by validating it here in service and not in directive.
this.hasImage = true;
/* ... */
}
function getNewJSON(){
return new MyNewJSON(origJSON); // it might be promise and you need resolve it in controller
}
}]);
So your HTML should look like:
<img ng-if="stateData.hasImage" ng-src="stateData.profilepic" alt=""/>
<div ng-if="!stateData.hasImage">
<div >{{stateData.firstName.charAt(1)+stateData.lastName.charAt(1)}}</div>
</div>
No directive needed.
Since image validation can take some time, you can put default image (some kind of placeholder)

Angular JS === comparison not working

I am trying to compare the value passed from the url to a controller to a field in a json file.
galleryItem.html
<div class="filter-box">
<ul class="filter list-inline text-center" ng-repeat="gal in ParentData">
<li></li>
</ul>
</div>
<div class="container-fluid">
<div class="row">
<div class="portfolio-box" ng-repeat="x in data">
<div class="col-sm-4">
<div class="item-img-wrap">
<img ng-src={{x.url}} class="img-responsive" alt="">
<div class="item-img-overlay">
<a href={{x.url}} class="show-image">
<span></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The updated controller:
controllers.controller('GalleryViewCtrl', function GalleryViewCtrl($scope, $http, $stateParams) {
$scope.pageName = '';
$scope.Description = '';
$scope.GalleryID = $stateParams.id;
$http.get('/data/galleryItems.json')
.then(function (response) { $scope.ParentData = response.data.galleries });
$http.get('/data/galleryItemImages.json')
.then(function (response) {
$scope.data = response.data.images.galleryIdentifier === $stateParams.id;
});
});
I verified the correct value is being passed in to the controller, the values are static and so is the data being passed from the json file. I placed an if statement to check for null as suggested as well. I removed it temporarily to reduce what I'm working with.
If I remove the === $stateParams.id i get all of the images returned and displayed correctly.
If I replace $stateParams.id with a value that I know is in the list (4 or '4') i do not get anything returned. I also tried the value for the last item in the list.
There are no errors (loading scripts, reading json etc.) and all of the values are correct when I'm debugging.
I am still new to this and there is so much documentation with different solutions it all gets very confusing. If anyone has any ideas they would be greatly appreciated.
You are loading data to the $scope.data when the ajax call returns some data. I am assuming your view code is calling the galleryFiltered even before that. May be try to add a null check before returning the value from the method.
$scope.galleryFiltered = function () {
if($scope.data!=null)
{
return $scope.data.galleryIdentifier === $scope.GalleryID;
}
return false;
};
Remember that $http service returns a promise so your $scope.data will be undefined (or holding current state) until $http.get('/data/galleryItemImages.json') will return a success callback function and assign new value to $scope.data from response.
If you'll run $scope.galleryFiltered() before promise gets resolved you will have $scope.data == undefined or whatever data is stored on $scope.data at the time or $scope.galleryFiltered() execution.

AngularJS scope in function does not seem to be working

I have two ajax calls. The data starts from the html (input), when enter is pressed, what is in the input field is sent to the controller then to the factory which makes the first ajax call. The success is handled back in the controller, then another ajax call is requested, and the data from that request is handled back in the controller again. I have a $scope -- $scope.ytChannel = data.items; in that final success within the function that does not seem to be working. Here is my code starting with the html
HTML:
<input class="form-control channel-index" type="text" ng-model="channel" placeholder="Enter Channel" ng-keydown="$event.which === 13 && cname(channel)"/>
JS:
.factory('ytVids', function($http){
return{
getChannel: function(name){
var url = 'https://www.googleapis.com/youtube/v3/channels? part=contentDetails&forUsername='+ name +'&key=[my api key]';
return $http.get(url);
},
getVids: function(channel){
return $http.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&maxResults=50&playlistId='+channel+'&key=[my api key]');
}
};
})
.controller('MainCtrl', function ($scope, ytVids) {
$scope.cname = function(channel){
ytVids.getChannel(channel).success(function(response){
//console.log(response);
console.log(response.items[0].contentDetails.relatedPlaylists.uploads);
ytVids.getVids(response.items[0].contentDetails.relatedPlaylists.uploads)
.success(function(data){
console.log('in success');
console.log(data.items);
$scope.ytChannel = data.items; // This is the scope that is not seeming to want to work.
});
});
};
});
And here is the html that calls that ytChannel
<ul>
<li ng-repeat="item in ytChannel" class="col-xs-12 col-sm-6 col-md-4 vid-options">
<a href="#">
<div class="title">{{item.snippet.title}}</div>
<img src="{{item.snippet.thumbnails.maxres.url}}" />
</a>
</li>
</ul>
if a scope is in a function, does the html not have access to it? What can be done so I can have access to the returned data?
Error
This is the error the console gives in the dev tools GET http://localhost:9000/%7B%7Bitem.snippet.thumbnails.maxres.url%7D%7D 404 (Not Found)
The correct code is
<img ng-src="{{item.snippet.thumbnails.maxres.url}}" />
As the manual says,
Using Angular markup like {{hash}} in a src attribute doesn't work
right: The browser will fetch from the URL with the literal text
{{hash}} until Angular replaces the expression inside {{hash}}. The
ngSrc directive solves this problem.

AngularJS Only Show View If Server Data Present

In my regular Javascript I append data to HTML ONLY if there's data from the server, otherwise I just show a simple div saying there's nothing. How can I implement this in AngularJS?
Example:
if (AJAXresult)
$element.append(JSONdata); //JSONdata will contain a list of customer data
else
$element.append('<div>No results</div>');
How can I achieve this in Angular?
The simplest way would be to control for the no data state in your returned view.
<div>
<div ng-if="!hasCustomers">
No Customers Available
</div>
<div ng-if="hasCustomers">
<!-- show some stuff -->
</div>
</div>
Then in your controller you can easily initialize this when you load your data:
angular.module('myApp').controller('MyController', function($scope, myDataService){
$scope.hasCustomers = false;
myDataService.getCustomers()
.then(function(value){
$scope.customers = value.data;
$scope.hasCustomers = customers && customers.length;
});
});
If you want to make sure the data is loaded before your view is ever instantiated, then you can also use the resolve property on your $route
$routeProvider.when('/someRoute/',{
templateUrl: '/sometemplate.html',
controller: 'MyController',
controllerAs: 'ctrl', // <-- Highly recommend you do this
resolve: {
customerData: function(myDataService){
return myDataService.getCustomers();
}
}
});
resolve is basically a hash of functions that return a promise, and can be dependency injected just like everything else. The controller and view will not be loaded until all the resolve promises have been fulfilled.
It will be available in your controller by the same property name you gave it:
angular.module('myApp')
.controller('MyController', function($scope, customerData){
$scope.customers = customerData;
$scope.hasCustomers = customerData && customerData.length;
});

Triggering function on ngclick with AngularJS

I have a function that makes a $http call to an external API and then populates some results within an ng-repeat array.
Right now the function gets triggered on every element on the ng-repeat, which creates a whole lot of server calls. I'd like for the function to only make the call once an element from the ng-repeat is clicked upon.
I've tried with ng-click, but i'd say i'm missing something.
The $http query that i'm trying to call on click is the second one:
function ImageCtrl($scope, $http) {
$scope.image = 'img/record-default.png';
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + $scope.artist.name + '&album=' + $scope.release.title + '&format=json').
success(function (data4) {
$scope.image = data4.album.image[2]['#text'];
}
)
function getVersions ($scope, $http){
$http.get('http://api.discogs.com/masters/' + $scope.release.id + '/versions').
success(function (data5) {
$scope.versions = data5.versions;
});
}
}
And the relevant html:
<div class="col-md-3" ng-controller="ImageCtrl" ng-repeat="release in releases | filter:album | filter:year | filter:{ role: \'main\' }" >
<div class="release" ng-click="getVersions()"> \
<img class="img-responsive" ng-src="{{image}}" /> {{release.title}}
<ul ng-controller="ImageCtrl">
<li ng-repeat="version in versions">{{version.format}}</li>
</ul>
</div>
</div>
And a working Plunker. Function in question is line 60 on script.js
So I ended up taking what you have shown and doing some refactoring.
I moved getVersions to the prototype, and use it to append versions to a release object instead of the $scope.
function ImageCtrl($scope, fakeService) {
var _this = this;
this.fakeService = fakeService;
this.$scope = $scope;
fakeService.getReleases()
.then(function (releases) {
$scope.releases = releases;
});
this.$scope.getVersions = function(release){
_this.getVersions(release);
};
}
ImageCtrl.$inject = ['$scope', 'fakeService'];
ImageCtrl.prototype.getVersions = function (release) {
this.fakeService.getVersions(release.id)
.then(function (versions) {
release.versions = versions;
});
};
The markup isn't terribly different, but you can see where I pass the actual release object into the getVersions function in the click event. This way it always acts directly on the object bound to that particular row.
<div class="row" ng-controller="ImageCtrl">
<div class="col-md-3" ng-repeat="release in releases">
<div class="release" ng-click="getVersions(release)">
<h1>{{release.title}}</h1>
<img class="img-responsive" height="100" width="100" ng-src="{{release.image}}" />
<ul>
<li ng-repeat="version in release.versions">{{version.format}}</li>
</ul>
</div>
</div>
</div>
And here is a working demo showing the whole thing in action: http://jsfiddle.net/jwcarroll/k6mkt/
I'm using a fake service here to mimic calling a web service in order to get the data. I highly recommend wrapping up your calls to $http in order to encapsulate data access in your controller.

Categories

Resources