How to set a native attribute from AngularJS directive? - angularjs

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;
};
});

Related

How to use an AngularjS View (.html file) without using it's tags

Inside a big AngularJS application I have a new HTML template file and a controller for it, and I'd like to build a layout the designer gave me using this temporary view, since I'd like to be able to call some data from the $scope object.
I also created a new route for it so that I have a clean working space.
But I don't want to include it in the main index.html file, like so:
<my-new-template></my-new-template>
I'd just like to start using it without having to include this HTML element anywhere, is this possible? This is the controller so far:
.directive('portfolio', [
function () {
return {
templateUrl: "views/temporary-view.html",
scope: {
data: "="
},
link: function (scope, element, attrs, ctrl) {
scope.stuff = 'stuff';
}
};
}])
The view:
<nav class="portfolio-view">
{{stuff}}
</nav>
Thanks for helping a noob like me! :)
In your directive, you can change the restrict option to change how the directive is called in the HTML. There are 4 options for this. I found this in the AngularJS documentation for directives:
restrict
String of subset of EACM which restricts the directive to a specific directive declaration style. If omitted, the defaults (elements and attributes) are used.
E - Element name (default): <my-directive></my-directive>
A - Attribute (default): <div my-directive="exp"></div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
By default, it uses EA, so as an Element (the way you do not want to call it in HTML) or an attribute.
If you wish to change it to, say, class for example, then change your directive definition to:
.directive('portfolio', [
function () {
return {
restrict: 'C',
templateUrl: "views/temporary-view.html",
scope: {
data: "="
},
link: function (scope, element, attrs, ctrl) {
scope.stuff = 'stuff';
}
};
}])
and you can call it as so:
<div class="portfolio"></div>
I think this is what you mean, and I hope it helps!
So I just changed .directive to .controller and posted it alongside the other controllers in the app and not the directives... I guess I was confused with that!
.controller('PortfolioView', ["$scope",
function ($scope) {
$scope.stuff = 'stuff';
}])

How to make sibling directives communication work( communication between certain specific directive)

All:
Suppose I have two directives( dir1 and dir2) , which are both isolated scope. From some posts, I learnt that I need to use "require" to get scope of the other directive, but there is one question confused me so much:
Suppose I use ng-repeat generated a lot of dir1 and dir2, how do I know in certain dir1, which specific dir2's controller scope is required( cos there are many dir2 and in my understanding, all those scopes in dir2 controller are independent to each other)?
For example:
app.directive("dir1", function(){
var counter = 0;
return {
restrict:"AE",
scope:{},
template: "<button class='dir1_btn'>highlight dir2_"+(couter++)+" </button>",
link:function(scope, EL, attrs){
EL.find("button.dir1_btn").on("click", function(){
// How to let according dir2 know this click?
})
}
}
})
app.directive("dir2", function(){
var counter = 0;
return {
restrict:"AE",
scope:{},
template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
link:function(scope, EL, attrs){
// Maybe put something here to listening the event passed from dir1?
}
}
})
And the html like( for simple purpose, I just put 2 of each there, actually it will generated by ng-repeat) :
<dir1></dir1>
<dir2></dir2>
<dir1></dir1>
<dir2></dir2>
Consider this just like the switch and light, dir1 is the switch to open(by change background-color) according light (dir2).
In actual project, what I want to do is angularJS directive version
sidemenu and scrollContent, each item in sidemenu is a directive,
click it will make according content(another directive) to auto scroll
to top.
I wonder how to do this? I know this is easy in jQuery, just wondering how to hook this into AngularJS data-driven pattern.
Thanks
The most important thing to note here is that I think you want to use ng-class Since you are creating both directives in an ng-repeat, I assume you are iterating over a list of objects (even if they are two separate ng-repeats, if you iterate over the same list of objects in both it will work. JQuery should not be necessary)? Attach an ngClass object to each object you iterate over, put it on an ng-class attribute in your dir2, then give dir1 access to change it. ngClass provides animation hooks if you want to animate the transition. The rest of my answer may help, though I would like to redo it now that I thought of ng-class. I have to get back to work for now though. I'll watch for feedback and try to answer quickly if you have questions.
I think there are probably a few ways to better accomplish what you are trying to do. It is not clear why both of your directives need to have isolate scopes. As I use angular more I find that though isolating a scope is a powerful technique, it is best to avoid over using it.
As for the require directive property, this post explains how to make directives communicate via their controllers very well.
I have two possible suggestions for you.
Make it one directive
Why can't you just put the templates into one?
Or if as I assume there is some reason they need to be separate, you could consider just sharing an object between them.
<div ng-repeat='obj in sharedDirObjs'>
<dir1 shared-dir-obj='obj'></dir1>
<dir2 shared-dir-obj='obj'></dir2>
</div>
app.controller('ctrl', function() {
$scope.sharedDirObjs = [obj1, obj2, obj3]
});
app.directive("dir1", function(){
var counter = 0;
return {
restrict:"AE",
scope:{sharedDirObj : '='},
template: "<button class='dir1_btn' ng-click='clickFn()'>highlight dir2_"+(couter++)+" </button>",
link:function(scope, EL, attrs){
var dir1vars...
scope.clickFn = function(){
// dir1 logic...
scope.sharedDirObj.dir2.clickFn(dir1vars...);
};
}
}})
app.directive("dir2", function(){
var counter = 0;
return {
restrict:"AE",
scope:{sharedDirObj : '='},
template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
link:function(scope, EL, attrs){
scope.sharedDirObj.dir2 = {};
scope.sharedDirObj.dir2.clickFn(dir1vars...) {
// access to dir2 vars
};
}
}})
Similarly, you could create a service that holds an array of objects that are shared by injecting the service and indexed using the $index from the ng-repeat, or you could use an id system as PSL suggests. Note that the solution I describe above could work with isolate scope, or without it using scope.$eval(attr.sharedObj); on either or both of your directives. The answer to this question provides a solid runthrough of when and why to use isolated scope. In any case it would likely be best not to pipe functions through a shared object as I am showing above, and timing issues would need to be dealt with. Better would be to store properties on the object and set a scope.$watch on them in your dir2.
You may have to use some sort of strategy. Some kind of identifier hook up. Clearly you cannot use require(to require the controller of a directive and you don't have any also it can only look up to ancestors or itself not siblings). For example you could add an id attribute and a for attribute and target the element with a selection based on specific attribute value and fire an event. With this position of related element does not matter.
Your directive could look like:
<dir1 dir-for="id1"></dir1>
<dir2 dir-id="id1"></dir2>
<dir1 dir-for="id2"></dir1>
<dir2 dir-id="id2"></dir2>
and simple implementation:
.directive("dir1", function($document) {
var counter = 0;
return {
restrict: "AE",
scope: {
dirFor: '#'
},
template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
link: function(scope, EL, attrs) {
var $target = angular.element(
$document[0].querySelector('[dir-id="' + scope.dirFor + '"]'))
.contents().scope();
var clicked = false;
scope.handleClick = function() {
clicked = !clicked;
targetScope.$broadcast("SWITCH_CLICKED", clicked);
}
scope.$on('$destory',function() {
$target = null;
}
}
}
})
app.directive("dir2", function() {
var counter = 0;
return {
restrict: "AE",
scope: {
dirId: '#'
},
template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
link: function(scope, EL, attrs) {
console.log(scope.$id);
scope.status = false;
scope.$on('SWITCH_CLICKED', function(e, data) {
scope.status = data;
});
}
}
});
Demo
var app = angular.module('app', []).controller('ctrl', angular.noop);
app.directive("dir1", function($document) {
var counter = 0;
return {
restrict: "AE",
scope: {
dirFor: '#'
},
template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
link: function(scope, EL, attrs) {
var $target = angular.element($document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents();
var clicked = false;
scope.handleClick = function() {
clicked = !clicked;
$target.scope().$broadcast("SWITCH_CLICKED", clicked);
}
scope.$on('$destroy',function() {
$target = null;
});
}
}
})
app.directive("dir2", function() {
var counter = 0;
return {
restrict: "AE",
scope: {
dirId: '#'
},
template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
link: function(scope, EL, attrs) {
scope.status = false;
scope.$on('SWITCH_CLICKED', function(e, data) {
scope.status = data;
});
}
}
})
.on{
color:green;
}
.off{
color:blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<dir1 dir-for="id1"></dir1>
<dir2 dir-id="id1"></dir2>
<dir1 dir-for="id2"></dir1>
<dir2 dir-id="id2"></dir2>
</div>
I have used $document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents().scope() to get hold of the scope, similarly you could do .controller to get hold of the controller instance as well. Current example is doing an absolute selection(with document), you could as well make it relative.

Angularjs - Pass argument to directive

Im wondering if there is a way to pass an argument to a directive?
What I want to do is append a directive from the controller like this:
$scope.title = "title";
$scope.title2 = "title2";
angular.element(document.getElementById('wrapper')).append('<directive_name></directive_name>');
Is it possible to pass an argument at the same time so the content of my directive template could be linked to one scope or another?
here is the directive:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
replace:true
};
})
What if I want to use the same directive but with $scope.title2?
You can pass arguments to your custom directive as you do with the builtin Angular-directives - by specifying an attribute on the directive-element:
angular.element(document.getElementById('wrapper'))
.append('<directive-name title="title2"></directive-name>');
What you need to do is define the scope (including the argument(s)/parameter(s)) in the factory function of your directive. In below example the directive takes a title-parameter. You can then use it, for example in the template, using the regular Angular-way: {{title}}
app.directive('directiveName', function(){
return {
restrict:'E',
scope: {
title: '#'
},
template:'<div class="title"><h2>{{title}}</h2></div>'
};
});
Depending on how/what you want to bind, you have different options:
= is two-way binding
# simply reads the value (one-way binding)
& is used to bind functions
In some cases you may want use an "external" name which differs from the "internal" name. With external I mean the attribute name on the directive-element and with internal I mean the name of the variable which is used within the directive's scope.
For example if we look at above directive, you might not want to specify another, additional attribute for the title, even though you internally want to work with a title-property. Instead you want to use your directive as follows:
<directive-name="title2"></directive-name>
This can be achieved by specifying a name behind the above mentioned option in the scope definition:
scope: {
title: '#directiveName'
}
Please also note following things:
The HTML5-specification says that custom attributes (this is basically what is all over the place in Angular applications) should be prefixed with data-. Angular supports this by stripping the data--prefix from any attributes. So in above example you could specify the attribute on the element (data-title="title2") and internally everything would be the same.
Attributes on elements are always in the form of <div data-my-attribute="..." /> while in code (e.g. properties on scope object) they are in the form of myAttribute. I lost lots of time before I realized this.
For another approach to exchanging/sharing data between different Angular components (controllers, directives), you might want to have a look at services or directive controllers.
You can find more information on the Angular homepage (directives)
Here is how I solved my problem:
Directive
app.directive("directive_name", function(){
return {
restrict: 'E',
transclude: true,
template: function(elem, attr){
return '<div><h2>{{'+attr.scope+'}}</h2></div>';
},
replace: true
};
})
Controller
$scope.building = function(data){
var chart = angular.element(document.createElement('directive_name'));
chart.attr('scope', data);
$compile(chart)($scope);
angular.element(document.getElementById('wrapper')).append(chart);
}
I now can use different scopes through the same directive and append them dynamically.
You can try like below:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
scope:{
accept:"="
},
replace:true
};
})
it sets up a two-way binding between the value of the 'accept' attribute and the parent scope.
And also you can set two way data binding with property: '='
For example, if you want both key and value bound to the local scope you would do:
scope:{
key:'=',
value:'='
},
For more info,
https://docs.angularjs.org/guide/directive
So, if you want to pass an argument from controller to directive, then refer this below fiddle
http://jsfiddle.net/jaimem/y85Ft/7/
Hope it helps..
Controller code
myApp.controller('mainController', ['$scope', '$log', function($scope, $log) {
$scope.person = {
name:"sangeetha PH",
address:"first Block"
}
}]);
Directive Code
myApp.directive('searchResult',function(){
return{
restrict:'AECM',
templateUrl:'directives/search.html',
replace: true,
scope:{
personName:"#",
personAddress:"#"
}
}
});
USAGE
File :directives/search.html
content:
<h1>{{personName}} </h1>
<h2>{{personAddress}}</h2>
the File where we use directive
<search-result person-name="{{person.name}}" person-address="{{person.address}}"></search-result>
<button my-directive="push">Push to Go</button>
app.directive("myDirective", function() {
return {
restrict : "A",
link: function(scope, elm, attrs) {
elm.bind('click', function(event) {
alert("You pressed button: " + event.target.getAttribute('my-directive'));
});
}
};
});
here is what I did
I'm using directive as html attribute and I passed parameter as following in my HTML file. my-directive="push" And from the directive I retrieved it from the Mouse-click event object. event.target.getAttribute('my-directive').
Insert the var msg in the click event with scope.$apply to make the changes to the confirm, based on your controller changes to the variables shown in ng-confirm-click therein.
<button type="button" class="btn" ng-confirm-click="You are about to send {{quantity}} of {{thing}} selected? Confirm with OK" confirmed-click="youraction(id)" aria-describedby="passwordHelpBlock">Send</button>
app.directive('ngConfirmClick', [
function() {
return {
link: function(scope, element, attr) {
var clickAction = attr.confirmedClick;
element.on('click', function(event) {
var msg = attr.ngConfirmClick || "Are you sure? Click OK to confirm.";
if (window.confirm(msg)) {
scope.$apply(clickAction)
}
});
}
};
}
])

Angular Directive Different Template

I have a directive myDirective with variable type. If I run <my-directive type="X"> I want the directive to use templateUrl: x-template.html.
If I do <my-directive type="Y"> I want the directive to use templateUrl: y-template.html.
This is my current directive.
app.directive('myDirective', function() {
var myDirective = {
templateUrl: 'X-template.html',
restrict: 'E',
scope: {
type: '='
},
};
return myDirective;
});
I read thru stackoverflow and angular documentation but have not found anything that I need.
I am now trying to do something along the lines of:
if ($scope.type === 'X') {
templateUrl: 'X-template.html',
}
else if ($scope.type === 'Y') {
templateUrl: 'Y-template.html',
}
But do not know where to do it.
Do you guys know if this is possible and how?
Angular will accept a function as the template option, so you could do something like so:
.directive('myDirective', function () {
return {
templateUrl: function (tElement, tAttrs) {
if (tAttrs) {
if (tAttrs.type === 'X') {
return 'X-template.html';
}
if (tAttrs.type === 'Y') {
return 'Y-template.html';
}
}
}
}
});
For more info, see the documentation for the $compile service.
You can work around this issue using ng-include inside compile:
app.directive('myDirective', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'' + attrs.type + '-template.html\'"></div>');
}
}
});
fiddle
If you're willing to live on the bleeding edge with a build on the 1.1.x code path (note the warning attached to every 1.1.x build notes entry so I don't dilute this answer by repeating it again here), you're in luck--this very feature was just added in the 1.1.4 release on April 3rd. You can find the release notes for 1.1.4 here and the feature's task log includes a Jasmine test that demonstrates how to use the new functionality.
If you're more conservative and are using a 1.0.x release, then you won't be able to accomplish this as easily, but it can be done. Mark Rajcok's solution looks like it would fit your requirements as-stated, but I would just add a few additional notes:
Aside from its 1.1.4 release, compile-time directives don't support modification at runtime.
As of 1.1.4, you can safely modify the attributes of compile-time directives, but only from another compile-time directive.
You may want to consider replaceWith() instead of append() since <my-directive> is not a standard-defined HTML element type.
If your X and Y templates contain additional directives, I don't think you'll be able to pass attributes on <my-template> through to the root element of your template so easily.
A directive with replace: true will transfer attributes from the source element to its replacement root, but I do not think that ngInclude will do the same from is host to the root of the included template.
I also seem to recall that ngInclude does not require that its template have exactly one root element.
You could perhaps preserve attributes on a replacement parent by using replaceWith() instead of append() and wrapping the <div ng-include=""> tag within a <div></div>. The outer <div> could hold attributes and would still be accessible after the <div ngInclude> element replaced itself with loaded content.
Be aware that ngInclude creates a new scope. This subjects you to a flashing yellow klaxons warning about the dangers of primitive scope models. For more information, see this fine page from Angular's GitHub depot.
I can propose another alternative for those on 1.0.x, but it involves a fair amount of code. It's a more heavy-weight operation, but it has the upside of not only being able of switching between templates, but full-fledged directives as well. Furthermore, its behavior is more readily dynamic.
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'partials/directive/my-directive.html',
link: function(scope, element, attrs, ctrl) {
// You can do this with isolated scope as well of course.
scope.type = attrs.type;
}
}
);
my-directive.js
<div ng-switch on="{{type}}">
<div ng-switch-where="X" ng-include="X-template.html"></div>
<div ng-switch-where="Y" ng-include="Y-template.html"></div>
</div>
my-directive.html
This is my version for optionally overriding a default template
templateUrl: function (elem, attrs) {
if (attrs.customTemplate) {
return '/path/to/components/tmpl/' + attrs.customTemplate + '.html';
} else {
return '/path/to/components/tmpl/directive.html';
}
}
e.g on a directive
<div my-directive custom-template="custom"></div>
I solve this problem so:
app.directive("post", function ($templateCache, $compile) {
function getTemplate(mode) {
switch (mode) {
case "create":
return "createPost.html";
case "view":
return "viewPost.html";
case "delete":
return "deletePost.html";
}
}
var defaultMode = "view";
return {
scope: {},
restrict: "AE",
compile: function (elem, attrs, transclude) {
return function ($scope, $element, $attr) {
function updateTemplate() {
$element.html("");
$compile($templateCache.get(getTemplate($scope.mode)).trim())($scope, function (clonedElement, scope) {
clonedElement.appendTo($element);
});
}
$scope.mode = $attr.mode || defaultMode;
$scope.$watch("mode", updateTemplate);
}
}
}
});
It's probably not the best way to do this, but I have no extra scope.
Ok, this might help someone here :-)
To inject your custom attr into your link or controller function use the following.
I'm at work right now but will post a fiddle later if I get a chance :-)
.directive('yourDirective', function() {
return {
restrict: 'EA',
template: '<div></div>', // or use templateUrl with/without function
scope: {
myAttibute: '#myAttr' // adds myAttribute to the scope
},
link: function(scope) {
console.log(scope.myAttibute);
}
}
}
// HTML ""
// Console will output "foo"

if a ngSrc path resolves to a 404, is there a way to fallback to a default?

The application I'm building requires my user to set 4 pieces of information before this image even has a chance of loading. This image is the center-piece of the application, so the broken image link makes it look like the whole thing is borked. I'd like to have another image take its place on a 404.
Any ideas? I'd like to avoid writing a custom directive for this.
I was surprised that I couldn't find a similar question, especially when the first question in the docs is the same one!
http://docs.angularjs.org/api/ng.directive:ngSrc
It's a pretty simple directive to watch for an error loading an image and to replace the src. (Plunker)
Html:
<img ng-src="smiley.png" err-src="http://google.com/favicon.ico" />
Javascript:
var app = angular.module("MyApp", []);
app.directive('errSrc', function() {
return {
link: function(scope, element, attrs) {
element.bind('error', function() {
if (attrs.src != attrs.errSrc) {
attrs.$set('src', attrs.errSrc);
}
});
}
}
});
If you want to display the error image when ngSrc is blank you can add this (Plunker):
attrs.$observe('ngSrc', function(value) {
if (!value && attrs.errSrc) {
attrs.$set('src', attrs.errSrc);
}
});
The problem is that ngSrc doesn't update the src attribute if the value is blank.
Little late to the party, though I came up with a solution to more or less the same issue in a system I'm building.
My idea was, though, to handle EVERY image img tag globally.
I didn't want to have to pepper my HTML with unnecessary directives, such as the err-src ones shown here. Quite often, especially with dynamic images, you won't know if it's missing until its too late. Adding extra directives on the off-chance an image is missing seems overkill to me.
Instead, I extend the existing img tag - which, really, is what Angular directives are all about.
So - this is what I came up with.
Note: This requires the full JQuery library to be present and not just the JQlite Angular ships with because we're using .error()
You can see it in action at this Plunker
The directive looks pretty much like this:
app.directive('img', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
// show an image-missing image
element.error(function () {
var w = element.width();
var h = element.height();
// using 20 here because it seems even a missing image will have ~18px width
// after this error function has been called
if (w <= 20) { w = 100; }
if (h <= 20) { h = 100; }
var url = 'http://placehold.it/' + w + 'x' + h + '/cccccc/ffffff&text=Oh No!';
element.prop('src', url);
element.css('border', 'double 3px #cccccc');
});
}
}
});
When an error occurs (which will be because the image doesn't exist or is unreachable etc) we capture and react. You can attempt to get the image sizes too - if they were present on the image/style in the first place. If not, then set yourself a default.
This example is using placehold.it for an image to show instead.
Now EVERY image, regardless of using src or ng-src has itself covered in case nothing loads up...
To expand Jason solution to catch both cases of a loading error or an empty source string, we can just add a watch.
Html:
<img ng-src="smiley.png" err-src="http://google.com/favicon.ico" />
Javascript:
var app = angular.module("MyApp", []);
app.directive('errSrc', function() {
return {
link: function(scope, element, attrs) {
var watcher = scope.$watch(function() {
return attrs['ngSrc'];
}, function (value) {
if (!value) {
element.attr('src', attrs.errSrc);
}
});
element.bind('error', function() {
element.attr('src', attrs.errSrc);
});
//unsubscribe on success
element.bind('load', watcher);
}
}
});
App.directive('checkImage', function ($q) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
attrs.$observe('ngSrc', function (ngSrc) {
var deferred = $q.defer();
var image = new Image();
image.onerror = function () {
deferred.resolve(false);
element.attr('src', BASE_URL + '/assets/images/default_photo.png'); // set default image
};
image.onload = function () {
deferred.resolve(true);
};
image.src = ngSrc;
return deferred.promise;
});
}
};
});
in HTML :
<img class="img-circle" check-image ng-src="{{item.profileImg}}" />
If image is 404 or image is null empty whatever there is no need for directives you can simply use ng-src filter like this :)
<img ng-src="{{ p.image || 'img/no-image.png' }}" />
I use something like this, but it assumes that team.logo is valid. It forces default if "team.logo" isn't set or is empty.
<img ng-if="team.logo" ng-src="https://api.example.com/images/{{team.logo}}">
<img ng-hide="team.logo" ng-src="img/default.png">
You don't need angular for that, or even CSS or JS. If you want, you can wrap this answer (linked) in a simple directive to make it simpler, like or something, but it's a pretty simple process... just wrap it in an object tag...
How to hide image broken Icon using only CSS/HTML (without js)
Is there a specific reason you can't declare the fallback image in your code?
As I understand, you have two possible cases for your image source:
Correctly set pieces of information < 4 = Fallback image.
Correctly set pieces of information == 4 = Generated URL.
I think this should be handled by your app - if the correct URL cannot currently be determined, instead pass a loading/fallback/placeholder image URL.
The reasoning is that you never have a 'missing' image, because you have explicitly declared the correct URL to display at any point in time.
I suggest that you might like to use the Angular UI Utils 'if statement' directive to solve your problem, as found at http://angular-ui.github.io/. I have just used it to do exactly the same thing.
This is untested, but you could do something like:
Controller code:
$scope.showImage = function () {
if (value1 && value2 && value3 && value4) {
return true;
} else {
return false;
}
};
(or simpler)
$scope.showImage = function () {
return value1 && value2 && value3 && value4;
};
HTML in View: <img ui-if="showImage()" ng-src="images/{{data.value}}.jpg" />
Or even simpler, you could just use a scope property:
Controller code:
$scope.showImage = value1 && value2 && value3 && value4;
HTML in View: <img ui-if="showImage" ng-src="images/{{data.value}}.jpg" />
For a placeholder image, just add another similar <img> tag but prepend your ui-if parameter with an exclamation (!) mark, and either make ngSrc have the path to the placeholder image, or just use a src tag as per normal ol' HTML.
eg. <img ui-if="!showImage" src="images/placeholder.jpg" />
Obviously, all of the above code samples are assuming that each of value1, value2, value3 and value4 will equate to null / false when each of your 4 pieces of information are incomplete (and thus also to a boolean value of true when they are complete).
PS. The AngularUI project has recently been broken in to sub-projects, and the documentation for ui-if seems to be missing currently (it's probably in the package somewhere though). However, it is pretty straightforward to use as you can see, and I have logged a Github 'issue' on the Angular UI project to point it out to the team too.
UPDATE: 'ui-if' is missing from the AngularUI project because it's been integrated in to the core AngularJS code! Only as of v1.1.x though, which is currently marked as 'unstable'.
Here's a solution I came up with using native javascript. I'm checking if the image is broken then adding a class to the image just in case and changing the source.
I got part of my answer from a Quora answer http://www.quora.com/How-do-I-use-JavaScript-to-find-if-an-image-is-broken
app.directive('imageErrorDirective', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element[0].onerror = function () {
element[0].className = element[0].className + " image-error";
element[0].src = 'http://img3.wikia.nocookie.net/__cb20140329055736/pokemon/images/c/c9/702Dedenne.png';
};
}
}
});
Came up with my own solution.
It replaces image both if src or ngSrc is empty, and if img returns 404.
(fork of #Darren solution)
directive('img', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
if((('ngSrc' in attrs) && typeof(attrs['ngSrc'])==='undefined') || (('src' in attrs) && typeof(attrs['src'])==='undefined')) {
(function () {
replaceImg();
})();
};
element.error(function () {
replaceImg();
});
function replaceImg() {
var w = element.width();
var h = element.height();
// using 20 here because it seems even a missing image will have ~18px width
// after this error function has been called
if (w <= 20) { w = 100; }
if (h <= 20) { h = 100; }
var url = 'http://placehold.it/' + w + 'x' + h + '/cccccc/ffffff&text=No image';
element.prop('src', url);
}
}
}
});
This will allow only to loop twice, to check if the ng-src doesn't exist else use the err-src, this prevents the continues looping.
(function () {
'use strict';
angular.module('pilierApp').directive('errSrc', errSrc);
function errSrc() {
return {
link: function(scope, element, attrs) {
element.error(function () {
// to prevent looping error check if src == ngSrc
if (element.prop('src')==attrs.ngSrc){
//stop loop here
element.prop('src', attrs.errSrc);
}
});
}
}
}
})();
This is a solution to your problem
Do this in your component.html
<img [src]="imageUrl" (error)="handleError()">
in your component.ts file
defaultImageUrl = '/assets/default-image.png';
imageUrl = 'https://example.com/actual-image.png';
create a function inside the component.ts file
handleError() {
this.imageUrl = this.defaultImageUrl;
}
this will solve your issues for you

Resources