AngularJS - load images using REST API calls - javascript

I am writing an application where I need to display car inventory. I ping an API to get all cars matching search criteria such as Car Make, Model and Year. I need to display an image of each car along with the other information. Once the JSON data is available, it also has an ID (StyleID) for each car in my results that I need to use to make another API call to request images for that car.
After reading a few articles (such as this one) I figured I need to use a custom directive in order to query and insert each car's image in a specific spot when looping over the results.
I read this custom directive tutorial by Jim Lavin to create my sample. I was hoping that this approach will work however I must be missing something as it simply doesn't execute my custom directive and display the car image as I want it to.
Can someone please help?
Here's the plunker that shows my code:
http://plnkr.co/edit/5DqAspT92RUPd1UmCIpn?p=preview
Here's the information about the specific media call to Edmunds API that I am trying to use.
And here's the URL to the media endpoint
Repeating my code :
My HTML code :
<div firstImageOfMyCar data-styleid="style.id"></div>
or
<firstImageOfMyCar data-styleid="style.id"></firstImageOfMyCar>
And here's my custom directive:
// Custom Directive to get first image of each car.
app.directive('firstImageOfMyCar', function() {
return {
restrict: "E",
link: function(scope, elm, attrs) {
// by default the values will come in as undefined so we need to setup a
// watch to notify us when the value changes
scope.$watch(attrs.styleid, function(value) {
//elm.text(value);
// let's do nothing if the value comes in empty, null or undefined
if ((value !== null) && (value !== undefined) && (value !== '')) {
// get the photos for the specified car using the styleID.
// This returns a collection of photos in photoSrcs.
$http.get('https://api.edmunds.com/v1/api/vehiclephoto/service/findphotosbystyleid?styleId=' + value + '&fmt=json&api_key=mexvxqeke9qmhhawsfy8j9qd')
.then(function(response) {
$scope.photoSrcs = response.photoSrcs;
// construct the tag to insert into the element.
var tag = '<img alt="" src="http://media.ed.edmunds-media.com' + response.photoSrcs[0] + '" />" />'
// insert the tag into the element
elm.append(tag);
}, function(error) {
$scope.error3 = JSON.stringify(error);
});
}
});
}
};
});

Angular normalizes an element's tag and attribute name to determine which elements match which directives. We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel). However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using dash-delimited attributes on DOM elements (e.g. ng-model).
Try
<div first-image-of-my-car data-styleid="style.id"></div>
or
<first-image-of-my-car data-styleid="style.id"></first-image-of-my-car>
Note: if you use the first, with the attribute, you will need to change the restrict in the directive to restrict: "A", (or "AE" to cover both cases)
Also, $http, and$scope are not defined in your directive. You can simply add $http to the directive function and DI will inject it. You probably want to use scope instead of $scope.
There were also some other things wrong with the example provided. Here is a working version: http://plnkr.co/edit/re30Xu0bA1XrsM0VZKbX?p=preview
Note that $http's .then() will call the provided function with data, status, headers, config, data will have the response you are looking for. (response.data[0].photoSrcs[0])

Please look at the answer by #TheScharpieOne. But i also played around with your code and api. And I would like to add, that your code might benefit from using angular services to wrap the api calls.
Here is an Example for a service:
app.service('VehicleService', function ($q, $http) {
this.getAllMakes = function () {
var deferred = $q.defer();
var url = 'https://api.edmunds.com/api/vehicle/v2/makes?state=new&view=basic&fmt=json&api_key=mexvxqeke9qmhhawsfy8j9qd'
$http.get(url).then(function (response) {
deferred.resolve(response.data.makes);
}, function (error) {
deferred.reject(new Error(JSON.stringify(error)));
});
return deferred.promise;
}
this.getCar = function (makeName, modelName, year) {
var deferred = $q.defer();
var url = 'https://api.edmunds.com/api/vehicle/v2/' + makeName + '/' + modelName + '/' + year + '?category=Sedan&view=full&fmt=json&api_key=mexvxqeke9qmhhawsfy8j9qd'
$http.get(url).then(function (response) {
deferred.resolve(response.data);
}, function (error) {
deferred.reject(new Error(JSON.stringify(error)));
});
return deferred.promise;
};
});
You could use it like this:
function CarCtrl($scope, VehicleService, VehiclePhotoService) {
// init make select
VehicleService.getAllMakes().then(function (value) {
$scope.makes = value;
});
$scope.getCars = function () {
VehicleService.getCar($scope.make.niceName, $scope.model.niceName, $scope.year.year)
.then(function (value) {
console.log(value);
$scope.myCars = value;
})
}
}
Here is a complete working jsfiddle: http://jsfiddle.net/gkLbh8og/

Related

How to make filters work with array in angularjs

Iam trying to create a custom filter to filter matching array of values in angularjs. Array Structure below
["tag1","tag2"]
Now I need to filter all objs having tags matching id1,id2.. Below is the filter I have tried
var autoFilter = angular.module("autoFilters",[]);
autoFilter.filter('arrayData', function (){
return function(){
return ["id1","id2"];
}
//$scope.arrayValues = ["id1","id2"];
});
and UI code below
<li style="cursor:pointer" ng-cloak class="list-group-item" ng-repeat="values in suggestionResults | arrayData">{{values.id}} -- {{values.title}}</li>
But Data is not showing up. Can you help me out where Iam doing wrong. Plunker Code available below
plunker here
see the code below :) This is not the best approach in my opinion and will definitely have some performance issue with larger lists, but it does the work (now I used indexOf(2) but there you can pass any truthy/falsy argument)
var autoFilter = angular.module("autoFilters",[]);
autoFilter.controller("filterController",['$scope','$http', function ($scope,$http) {
$scope.searchSuggest = function(){
//$http({method: 'GET', url: 'json/searchSuggestions.json'}).success(function(data) {
$http.get("assets.json").then(function(response) {
//var str = JSON.stringify(response);
//var arr = JSON.parse(str);
$scope.suggestionResult = response.data;
console.log($scope.suggestionResult);
//$scope.arrayData = ["asset_types:document/data_sheet","asset_types:document/brochure"];
}).catch(function activateError(error) {
alert('An error happened');
});
}
$scope.showProduct = function(){
}
}]);
autoFilter.filter('arrayData', function (){
return function(data){
// if you are using jQuery you can simply return $.grep(data, function(d){return d.id.indexOf('2') >-1 });
return data.filter(function(entry){
return entry.id.indexOf('2') > -1
})
}
});
Having experienced working with large lists I would, however, suggest you to avoid using a separate filter for this and rather manipulate it in the .js code. You could easily filter the data when you query it with your $http.get like:
$scope.suggestionResult = response.data.filter(function(){
return /* condition comes here */
}
This way you are not overloading the DOM and help the browser handling AngularJS's sometimes slow digest cycle.
If you need it to be dynamic (e.g. the filtering conditions can be changed by the user) then add an ng-change or $watch or ng-click to the modifiable information and on that action re-filter $scope.suggestionResult from the original response.data

AngularJS and Restangular, trying to convert update method to API

I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend

angular js select list selected populate after http response

I have a select list that needs to default to a certain value based on an object from an http response:
<select ng-model="isRentVals.cur" ng-options="v for v in isRentVals.values"></select>
The object from the response is called my_property:
function editPropController($scope, $http) {
$scope.my_property;
$http.get('/api/propertyById/' + $scope.prop_id)
.success(function(properties) {
$scope.my_property = properties[0];
$scope.isRentVals = { "cur" : $scope.my_property.is_rented, "values" : ["true", "false"]};
})
.error(function(err) {
alert('We got an error: ' + err);
});
The response from the $http call will come after the select element is bound to isRentVals.cur. isRentVals is not defined yet (will be defined in in the success callback in some milliseconds later) angular inserts an empty val then binds. How can I work around this?
The value is defaulted to blank and I have to reset my_property.is_rented in an ng_click method to save the new values.
The response for your $http call will come after your select element is bound to isRentVals.cur. Because isRentVals is not defined yet (will be defined in in your success call back in some milliseconds later) then angular insert an empty item to your select element to be able to bind to current value of isRentVals.cur. Also for better answers you should create a plunker.
Initialize isRentVals like this:
$scope.isRentVals = {}; //initialize as object
and overwrite the model when the data loaded:
function editPropController($scope, $http) {
$scope.my_property;
$scope.isRentVals = {};
$http.get('/api/propertyById/' + $scope.prop_id)
.success(function(properties) {
$scope.my_property = properties[0];
$scope.isRentVals = { "cur" : $scope.my_property.is_rented, "values" : ["true", "false"]};
})
.error(function(err) {
alert('We got an error: ' + err);
});

Angular directive to dynamically set attribute(s) on existing DOM elements

I'm somewhat new to Angular, so feedback on alternative approaches is certainly welcome.
I have created a directive called "serverMaxLengths". When the directive is placed on an ng-form, it will fetch database fields lengths from a REST API and then will walk the contents of all input elements contained within the form controller, and will set the "maxlength" attribute accordingly. The directive is as follows:
myApp.directive('serverMaxLengths', function ($log,$http,$compile) {
return {
restrict: 'A',
require: '^form',
link: function (scope, elem, attrs, formController) {
if (!formController) return;
var httpConfig = {
method: 'GET',
url: myAppRestURL + "/validator-rest?action=getDBFieldLengths"
};
$http(httpConfig)
.success(function (data, status, headers, config) {
if (typeof data.isValid != 'undefined') {
if(data.isValid){
var inputElem = elem.find('input');
angular.forEach(inputElem, function (value, key) {
var thisElement = angular.element(value);
if (typeof thisElement[0] !== 'undefined') {
if(typeof data.dbFieldLengths[thisElement[0].id] !== 'undefined'){
if(data.dbFieldLengths[thisElement[0].id] > 0){
thisElement.prop("maxlength", data.dbFieldLengths[thisElement[0].id]);
thisElement.prop("ng-maxlength", data.dbFieldLengths[thisElement[0].id]);
thisElement.prop("ng-minlength", 0);
$compile(thisElement)(scope);
}
}
}
});
}else{
...
}
}else{
...
}
}).error(function (data, status, headers, config) {
...
});
}
};});
This works. Insofar as I understand, $compile is replacing the existing element(s) when the directive is executed.
I'm wondering what a better "Angular" way of achieving this might be? I wanted a very simple solution that doesn't require the directive to be placed on any of the actual input elements(I want everything to happen in one go).
Lastly, one of the fields that is getting the max length set has a UI Bootstrap Typeahead directive assigned to it. Prior to the application of the "maxlength", the directive works as expected. However, post application of the "maxlength" being set on the field via the aforementioned method, type ahead renders a "TypeError: Cannot read property 'length' of undefined" error when the input looses focus(otherwise it works). This has me concerned about this approach, and what's happening behind the scenes.
*Note: The type ahead error is resolved by doing:
$compile(thisElement.contents())(scope);
Instead of:
$compile(thisElement)(scope);
Thanks for any feedback/suggestions/thoughts.
The addition of $compile(thisElement.contents())(scope); resolved the issue that was of primary concern.

save $location parameters state AngularJS

How do I save URL parameters state throughout lifecycle of application using pushState?
Page load.
Go to "/search" via href
submitSearch() through filter fields where $location.search(fields)
Go to "/anotherPage" via href
Go back to "/search" via href
Search paramters are set back to what they last were.
Is this a built in feature somewhere?
If not what's the best way to go about this?
If you're planning on a mostly single page website through pushState, you might want to get an intimate understanding of $routeProvider (http://docs.angularjs.org/api/ngRoute.%24routeProvider).
To go further down the rabbit hole, I would recommend looking at the ui-router module: (https://github.com/angular-ui/ui-router). $stateProvider (from ui-router) and $routeProvider work very similar, so sometimes the ui-router docs can give insights that you can't find in the poor documentation of the $routeProvider.
I reccomend going through the five page ui-router documentation (https://github.com/angular-ui/ui-router/wiki) page by page.
After all that preamble, here's the practical: you would set up a factory that holds history data and use the controller defined in your $routeProvider/$stateProvider to access and manipulate that data.
Note: the factory is a service. A service is not always a factory. The namespace goes:
angular.module.<servicetype[factory|provider|service]>.
This post explains the service types: https://stackoverflow.com/a/15666049/2297328. It's important to remember that they're all singletons.
Ex:
var myApp = angular.module("myApp",[]);
myApp.factory("Name", function(){
return factoryObject
});
The code would look something like:
// Warning: pseudo-code
// Defining states
$stateProvider
.state("root", {
url: "/",
// Any service can be injected into this controller.
// You can also define the controller separately and use
// "controller: "<NameOfController>" to reference it.
controller: function(History){
// History.header factory
History.pages.push(History.currentPage);
History.currentPage = "/";
}
})
.state("search", {
url: "/search",
controller: function(History, $routeParams) {
History.lastSearch = $routeParams
}
});
app.factory('<FactoryName>',function(){
var serviceObjectSingleton = {
pages: []
currentPage: ""
lastSearch: {}
}
return serviceObjectSingleton
})
If you're wondering what the difference between $routeProvider and $stateProvider is, it's just that $stateProvider has more features, mainly nested states and views... I think.
The easiest way is using cookies, angularjs provides a wrapping service for that.
Simply when you go to "/search" save your current URL parameters with "$cookieStore.put()" and once you've back you've got what you need with "$cookieStore.get()".
See the documentation at angularjs cookie store
I made a locationState service, you simply give it the values you want to persist and it stores them in the URL. So you can store all the state you want across all routes in your app.
Use it like this:
angular.module('yourapp')
.controller('YourCtrl', function ($scope, locationState) {
var size = locationState.get('size');
;
// ... init your scope here
if (size) {
$scope.size = size;
}
// ...and watch for changes
$scope.$watch('size', locationState.setter('size'));
}
Here's the code:
// Store state in the url search string, JSON encoded per var
// This usurps the search string so don't use it for anything else
// Simple get()/set() semantics
// Also provides a setter that you can feed to $watch
angular.module('yourapp')
.service('locationState', function ($location, $rootScope) {
var searchVars = $location.search()
, state = {}
, key
, value
, dateVal
;
// Parse search string
for (var k in searchVars) {
key = decodeURIComponent(k);
try {
value = JSON.parse(decodeURIComponent(searchVars[k]));
} catch (e) {
// ignore this key+value
continue;
}
// If it smells like a date, parse it
if (/[0-9T:.-]{23}Z/.test(value)) {
dateVal = new Date(value);
// Annoying way to test for valid date
if (!isNaN(dateVal.getTime())) {
value = dateVal;
}
}
state[key] = value;
}
$rootScope.$on('$routeChangeSuccess', function() {
$location.search(searchVars);
});
this.get = function (key) {
return state[key];
};
this.set = function (key, value) {
state[key] = value;
searchVars[encodeURIComponent(key)] = JSON.stringify(value);
// TODO verify that all the URI encoding etc works. Is there a mock $location?
$location.search(searchVars);
};
this.setter = function (key) {
var _this = this;
return function (value) {
_this.set(key, value);
};
};
});

Categories

Resources