0

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);
            });
          } 
        });
      }
    }; 
  });
1
  • Why wouldn't you just add the image URL to your scope and add it to the template like you are doing for the rest of the data? Commented Sep 23, 2014 at 17:05

2 Answers 2

3

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])

Sign up to request clarification or add additional context in comments.

3 Comments

Another note that is not really related, it appears you are being rate limited by the API. Making too many requests in a short period of time. Some of the requests are coming back 403s with message: "Account Over Queries Per Second Limit"
thank you so much. Also, thanks for the other tips. You also said that there were some other things wrong with my code. Can you please tell me what you think could be improved? I have been thinking of moving the REST calls to individual service. Is there anything else? Thanks again.
snies has an example of how to benefit from using a service to abstract the server called and consolidate them. There is not need to do the $q things he is doing though, just return the $http call (it returns its own promise, no need to make your own).
2

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/

5 Comments

thank you so much, this is really awesome! I was planning to do this and wasn't sure how to go about it. +1
I cleaned snies example up a little to remove all of the unneeded promises and $q DIs, jsfiddle.net/gkLbh8og/1 It still works the same way, just a little cleaner and easier to read. (I also removed the try-catch as they cannot be optimized by most JS compilers and are usually not recommended if they can be avoided.
@TheSharpieOne +1 for looking at my code. I have two questions concerning the modified fiddle jsfiddle.net/gkLbh8og/1:
1) Won't delay += 500 make the service slower with each call? and 2) Why can i skip the deferred stuff and still use then on VehicleService.getAllMakes()? response.data.makes is not a promise, is it? So is it just because of the prior promise from $http?
1) Yes, its just a cheap way to make the calls go at .5s, 1s, 1.5s, 2s... and so on. (something would need to be added to reset the delay when a new search is performed). 2) What is returned in the resolve for the promise is passed to the next function in the promise chain. The returned $http chains the promises, then when resolved, what is returned in the function (is not a promise, think express middleware) to passed to the next function in the chain. You can actually change the data between each resolve if wanted. This is also how the http interceptors work to allow you can change data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.