AngularJS - showing placeholder if image doesn't exist - angularjs

I have a list of items in some search results, some items have images, others do not. For those those items that do not have images I want to show a placeholder.
What would be the best approach to this?
I am currently trying to do it with a function (imageURL) and passing it the image details for the current item in the loop/repeat.
<ion-item ng-repeat="listing in searchResults.listings">
<div class="item item-image">
<img src="{{::searchResults.imageURL(listing._id,listing._source.filename1)}}">
</div>
.....
Inside the controller (controller as SearchResults):
self = this;
.....
self.imageURL = function(listing_id, listing_filename) {
if (angular.isUndefined(listing_id) || angular.isUndefined(listing_filename)) { //No image
return placeholderImage; //We want to show placeholderImage
}
return self.imageURL + listing_id + "/" + listing_filename; //Show the image
};
I will also be using a lazy loader directive at a later point, so will not be using the "src" attribute directly at that time.
Thanks

I would create a directive that replaces the src attribute, like ng-src does, and internally handles the logic you currently have in your imageURL function.
If the directive is named image-url then the markup would look something like this:
<img image-url="listing">
And your directive would look something like this:
app.directive('imageUrl', function () {
return {
scope: {
imageUrl: '='
},
link: function (scope, element, attrs) {
var imageUrl;
if (angular.isUndefined(scope.listing._id) || angular.isUndefined(scope.listing._source.filename1)) { //No image
imageUrl = placeholderImage; //We want to show placeholderImage
}
else {
imageUrl = "/" + scope.listing._id + "/" + scope.listing._source.filename1; // Set the src attribute
}
element.attr("src", imageUrl); // Set the src attribute
}
};
});
You really don't want to bind directly to the src attribute for reasons described in the ngSrc documentation

Use ng-src rather than src.
From angular docs;
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.

Related

Angular extending anchor element with directive on interpolated values

I have angular on the front-end of an application with html characters being interpolated and rendered. The data is coming from a backend CMS.
Almost all of the anchor values are linking to the value of their inner text.
For example:
http://google.com
Instead of repeatedly entering this same pattern I'd like to extend the a tag with a directive:
app.directive('a', function(){
return{
link: function(scope, element, attrs){
var value = $(element)[0].innerText;
if(!attrs.href){
attrs.href = value;
}
if(!attrs.target){
attrs.target = '_blank';
}
}
}
})
My data is coming into angular through bindings like this:
<div class="issue-article-abstact">
<h6 class="main-section" realign>Abstract</h6>
<p ng-bind-html="article.abstract | to_trusted"></p>
</div>
"article.abstract" would be a json object containing <a>http://google.com</a>
This currently only picks up anchor tags that are not rendered on the page through interpolation. Is it possible to create a directive that will see values on the page from bindings and extend their functionality through a directive like this?
Angular doesn't compile html that is inserted using ng-bind-html.
There are third party modules you can use to do it, however you could also do the conversion in a service, controller, custom filter or httpInterceptor before data gets inserted.
Following uses jQuery since it seems you are including it in the page
Simple example:
function parseLinks(html) {
// create container and append html
var $div = $('<div>').append(html),
$links = $div.find('a');
// modify html
$links.not('[href]').attr('href', function(i, oldHref) {
return $(this).text();
});
$links.not('[target]').attr('target', '_blank');
// return innerHtml string
return $div.html();
}
$http.get('/api/items').then(function(resp){
var data = resp.data;
data.forEach(function(item){
item.abstract = parseLinks(item.abstract);
});
return data;
});
This will be more efficient than having to compile all of this html in the dom using directive also

Create a nested fallback src for an Image using angularjs directive

In my tag if the src returns a 404 then I can display a fallback image using directives, but if this fallback image is also returns 404 the how can I show another image using directive
Create a directive to go through a series of error images and provide them one after the other.
You can provide alternative image urls in the tag itself.
<img fallbacksrc="http://url1/error.jpg,http://url2/error.jpg" src="http://url0.image.jpg">
Then write a directive for fallbacksrc and bind the tag for error event. Use split function to alternative images in to an array. You can then choose from this array inside the link function.
The information you are looking for is that the error event will occur any number of times as long as the src fails. So there is no limit for this to occur if all the images you are setting inside the directive fails continuously.
Here is a sample code. I'm using an array of error images in the scope itself in this example without providing them inside the tag.
function MyCtrl($scope) {
$scope.image = "http://greentreesarborcareinc.com/wp-content/uploads/2014/01/image-placeholder.jpg1"
$scope.errorImageIdx = 0;
$scope.errorImages = ["http://spanning.com/assets/uploads/images/11954453151817762013molumen_red_square_error_warning_icon.svg_.med_.png", "http://fivera.net/wp-content/uploads/2014/03/error_z0my4n.png"]
}
myApp.directive('fallbacksrc', function() {
return {
link: function(scope, ele) {
ele.bind('error', function() {
if (scope.errorImageIdx <= scope.errorImages.length - 1) {
angular.element(this).attr("src", scope.errorImages[scope.errorImageIdx]);
scope.errorImageIdx++;
}
});
}
}
});
Here the tag will try to display the image referenced in $scope.image. But that is invalid. So, it tries to load the images from the array.
Try setting the first element of the array to something invalid. It will automatically select the second image in this case.
You can create angular directive like this -
app.directive('onError', function() {
return {
restrict:'A',
link: function(scope, element, attr) {
element.on('error', function() {
element.attr('src', attr.onError);
})
}
}
});
And use like -
<img class="pic" on-error="default-image.jpg" ng-src="{{author.profileImageUrl}}">

resulting HTML from $compile(custom-directive) doesn't bind {{values}}

I want to dynamically add Angular custom Directives, but the directive resulting from $compile(directive) doesn't have the 2-ways binding.
Here's my simplified problem: I am using MapBox, and I want to use Directives for the the markers' popup to show, for example, the markers' title. MapBox wants HTML as a String to put inside the popup, so my idea was to pass a $compiled directive, something like $compile('<myDirective></myDirective>')($scope).html().
It replace the directive with its template, but {{values}} are not solved.
I have something like this to create the popup
map.featureLayer.on('layeradd', function(e)
{
var marker = e.layer;
var popupContent = ctrl.createPopup(marker);
// popupContent should be HTML as String
marker.bindPopup(popupContent);
});
ctrl.createPopup(marker) call a function of the controller, that does:
this.createPopup = function(marker)
{
var popup = "<mapbox-marker-popup"
+" title = "+marker.feature.properties.title
+"</mapbox-marker-popup>";
// should return HTML as String
return ($compile(popup)($scope).html());
}
where mapbox-marker-popup is a directive specified as follow:
/* ===== MARKER POPUP DIRECTIVE=========== */
.directive('mapboxMarkerPopup', function() {
return {
restrict: 'E',
template: [
"<p>{{title}}</p>",
].join(""),
scope:
{
title: '#'
}
}
})
Anyway... mapboxMarkerPopup is not working. title is shown as {{title}}
[UPDATE2 - {{title}} not solved]
Here's the JSFiddle
You need to return the compile angular element instead of returning html of that element. Only returning the html will never carry the angular two way binding. By using compiled object you can keep your binding working.
Code
this.createPopup = function(marker) {
var popup = "<mapbox-marker-popup" +
"title = '" + marker.feature.properties.title + "'"
+ "</mapbox-marker-popup>";
return ($compile(popup)($scope)[0]);
};
Working Fiddle
Update
$compile
Compiles an HTML string or DOM into a template and produces a template
function, which can then be used to link scope and the template
together.
Take a look at this link will give you more idea

angularjs: only set background-image on ngStyle if url is defined

I have a property on the scope which can hold an url (bgUrl) of an image. Now I would like to use this image as a background image of a 'div'. However, if the property is undefined I would like to use a default image which is defined by the class bg-image. Now I can do it like
<img ng-show="bgUrl" ng-src="bgUrl"/>
<div ng-hide="bgUrl" class="bg-image"/>
However, I would like to combine this into one element. I tried something like this
<div class="gb-image" ng-style="{true: 'background-image': 'url(\'' + bgUrl + '\')'}[bgUrl]"/>
I just copied some code from google (no idea if this can ever work). One thing is clear though, this doesn't work :)
What would be a nice angular way to solve this and can my solution work ?
Right way is a directive way:
app.directive('bg', function () {
return {
link: function(scope, element, attrs) {
if(scope.bgUrl){
element.css("background-image","url("+scope.bgUrl+")");
} else {
element.addClass("bg-default");
}
}
}
});
Take a look: http://plnkr.co/edit/xYEjpp?p=preview

How to set a native attribute from AngularJS directive?

I'd like to write HTML similar to:
test
<img src="sharedasset: img.png"/>
And have a directive called "sharedasset" that gets the full path to img.png and sets the value of the attribute without the directive having any knowledge of what the attribute name is ahead of time. Is this possible?
Update
Since I originally posted this there have been some improvements to Angular and I thought I'd share what I do now as a result. In the HTML I use Guido Bouman's answer which is to create a filter and, now with Angular's bind once feature, this makes it the best option in my opinion.
In the JS code though, instead of injecting $filter and my globalVars constant everywhere, now I just prepend the word static to any path of an asset that is hosted on the static content server like {templateUrl: "static/someTemplate.html"} and then use an Angular HTTP Interceptor to look for any path that begins with "static" and replace it with the domain for the static server. Very simple.
<a full-path="img.png">test</a>
<img full-path="img.png">
app.directive('fullPath', function() {
return {
link: function(scope, element, attrs) {
var fullPathUrl = "http://.../";
if(element[0].tagName === "A") {
attrs.$set('href',fullPathUrl + attrs.fullPath);
} else {
attrs.$set('src',fullPathUrl + attrs.fullPath);
}
},
}
});
I don't know where you are getting fullPathUrl from, so I hardcoded it in the link function.
I didn't want the directive to care what the attribute name was, so this is what I ended up doing:
<a shared-asset="images/img.png" attr="href">test</a>
<img shared-asset="images/img.png" />
app.directive('sharedAsset', function (globalVars) {
return {
restrict: "A",
scope: {
attr: "#attr"
},
link: function (scope, element, attrs) {
var fullPath = globalVars.staticWebsite + "/app/styles/main/" + attrs.sharedAsset + "?build=" + globalVars.buildNumber;
attrs.$set(scope.attr || "src", fullPath);
}
};
});
Update: I changed it to default to the "src" attribute since images will be the most common scenario.
A custom filter is much more suited for this case than a directive:
test
<img src="{{'images/img.png' | fullPath}}" />
The filter: (Assuming you have a global filters module)
angular.module('filters').filter('fullPath', function(globalVars) {
return function(url) {
return globalVars.staticWebsite + "/app/styles/main/" + url + "?build=" + globalVars.buildNumber;
};
});

Resources