I've used this sort of directive before for a fallback image if the image does not load correctly.
app.directive('fallbackSrc', function () {
var fallbackSrc = {
link: function postLink(scope, element, attrs) {
element.bind('error', function() {
angular.element(this).css("background-image", attrs.fallbackSrc);
});
}
}
return fallbackSrc;
});
This works great when it is placed on the html like this, of course the directive would replace the src of the image instead of modifying the css:
<img fallback-src="http://google.com/favicon.ico" ng-src="{{image}}"/>
I currently have a background-image though:
<div class="issue-gallery-container" fallback-src="http://google.com/favicon.ico" style="background-image: url({{ AWS }}images/cover/{{ item.volume.number }}.{{ item.number }}.png)">
</div>
The directive right now does not pick up the error on the element since it occurs in the elements css. How would I modify the directive to listen for an error on the elements background-image?
I would setup a "dummy" img directive that changes its parent css. Or you could create a a template to simplify things even more.
Here is a working plunker http://plnkr.co/edit/334WIH2VUGReVTUYt2Tb?p=preview
Code is a bit messy but it works.
app.directive('backgroundFallbackSrc', function () {
return {
link : function(scope, element, attrs) {
element.bind('error', function() {
element.parent().css('background-image', 'url("' + attrs.backgroundFallbackSrc + '")');
});
}
}
});
html
<div class="issue-gallery-container" style="display:block; height:2000px;">
<img background-fallback-src="http://keithfimreite.com/BlogFiles/keithfimreite/SAAS.jpg"
ng-src="{{invalidImage}}" style="display:none;">
</div>
Related
I've been searching for an answer to simple but not trivial question: What is a right way to catch image' onload event in Angular only with jqLite? I found this question , but I want some solution with directives.
So as I said, this is not accepted for me:
.controller("MyCtrl", function($scope){
// ...
img.onload = function () {
// ...
}
because it is in controller, not in directive.
Here's a re-usable directive in the style of angular's inbuilt event handling directives:
angular.module('sbLoad', [])
.directive('sbLoad', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var fn = $parse(attrs.sbLoad);
elem.on('load', function (event) {
scope.$apply(function() {
fn(scope, { $event: event });
});
});
}
};
}]);
When the img load event is fired the expression in the sb-load attribute is evaluated in the current scope along with the load event, passed in as $event. Here's how to use it:
HTML
<div ng-controller="MyCtrl">
<img sb-load="onImgLoad($event)">
</div>
JS
.controller("MyCtrl", function($scope){
// ...
$scope.onImgLoad = function (event) {
// ...
}
Note: "sb" is just the prefix I use for my custom directives.
Ok, jqLite' bind method doing well its job. It goes like this:
We are adding directive' name as attribute in our img tag . In my case , after loading and depending on its dimensions , image have to change its class name from "horizontal" to "vertical" , so directive's name will be "orientable" :
<img ng-src="image_path.jpg" class="horizontal" orientable />
And then we are creating simple directive like this:
var app = angular.module('myApp',[]);
app.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
element.bind("load" , function(e){
// success, "onload" catched
// now we can do specific stuff:
if(this.naturalHeight > this.naturalWidth){
this.className = "vertical";
}
});
}
}
});
Example (explicit graphics!): http://jsfiddle.net/5nZYZ/63/
AngularJS V1.7.3 Added the ng-on-xxx directive:
<div ng-controller="MyCtrl">
<img ng-on-load="onImgLoad($event)">
</div>
AngularJS provides specific directives for many events, such as ngClick, so in most cases it is not necessary to use ngOn. However, AngularJS does not support all events and new events might be introduced in later DOM standards.
For more information, see AngularJS ng-on Directive API Reference.
I have a template, inside that template is div with the class big-box and it's inside a div container called .movie-container.
Template,
.movie-container
.big-box
I want to animate the .big-box element to a height of 300px (from 0px) when a user clicks on the .movie-container.
I've tried to setup a directive with a click function but it's not working. When I click on the .big-box div it doesn't show the click in the console log.
app.directive('big-box', [function() {
return {
link: function(scope, elem, attrs) {
elem.bind('click', function() {
console.log ('click')
});
}
}
}]);
So I could use some tips.
The naming convention used for the directive is not correct. You have to use camel case while defining your custom directives.
app.directive('bigBox', [function() { // Use camel case
return {
link: function(scope, elem, attrs) {
elem.bind('click', function() {
console.log ('click')
});
}
}
}]);
and in your html you need to use it like this :
<div class="movie-container">
<div big-box> <!-- using the directive -->
click here
</div>
</div>
The name must be written using dashes and each dash represents a capital letter from the directive definition. It is a angularjs convention.
Here is a working fiddle.
HTML
<div my-dir>
<tour step=" currentstep">
<span tourtip="Few more steps to go."
tourtip-next-label="Close"
tourtip-placement="bottom"
tourtip-offset="80"
tourtip-step="0">
</span>
</tour>
</div>
I have written below directive to detect the x element of tour directive.But it always shows the parent div element even though I have clicked the x.So how can I do this ? Thanks in advance.
Directive
.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
element.on('click', function(e) {
scope.$apply(function() {
if (element[0].className === 'tour-close-tip') {
console.log('my task');
}
});
e.stopPropagation(); //stop event from bubbling up to document object
});
}
};
}
]);
UI
This is the generated HTML on the browser:
<div hide-element-when-clicked-out-side="" class="ng-scope">
<tour step=" currentstep" class="ng-scope">
<span tourtip="Few more steps to go.!" tourtip-next-label="Close" tourtip-placement="bottom" tourtip-offset="80" tourtip-step="0" class="ng-scope">
</span><div class="tour-tip" tour-popup="" style="display: block; top: 80px; left: 0px;">
<span class="tour-arrow tt-bottom"></span>
<div class="tour-content-wrapper">
<p ng-bind="ttContent" class="ng-binding">Few more steps to go.!</p>
<a ng-click="setCurrentStep(getCurrentStep() + 1)" ng-bind="ttNextLabel" class="small button tour-next-tip ng-binding">Close</a>
<a ng-click="closeTour()" class="tour-close-tip">×</a>
</div>
</div>
Can you tell me how to access class="tour-close-tip" element within the above directive ? For me it always shows the ng-scope as the class.
You can either bind directly to that element or check which element has been clicked on, using the target attribute:
element.on('click', function (e) {
scope.$apply(function () {
if (angular.element(e.target).hasClass('tour-close-tip')) {
Your eventListener is not on the X but on the outer div element. One option would be to add the listener to the X element using a query selector on the element
You could try something like the following to get the X span and add the listener
element[0].querySelector('span').on...
Another probably better approach would be to use event delegation such as
element.on('click', selector, function(e){
});
Edit: I see your comment regarding not using JQuery so this may not work as Angular doesn't support event delegation with .on as far as I am aware.
you could use this:
app.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var x = angular.element(document.querySelector('.tour-close-tip'));
x.bind('click', function() {
console.log('clicked');
});
}
};
}
]);
here's a demo plnkr:
http://plnkr.co/edit/cUCJRetsqKmSbpI0iNoJ?p=preview
there's a heading with class 'tour-close-tip' there, and we attached a click event to it.
try it out, click the heading and look in your browser's console.
from this demo hopefuly you can make progress with your code.
I want to show user image if image exist, like :
<img alt="user_image" src="/images/profile_images/{{result.image}}">
And if user image not available then should show default image , Like
<img alt="user_image" src="/images/default_user.jpg">
How can I do this through angularjs in html page ?
You could create a directive checkImage to check if the image really exist:
<img check-image ng-src="{{img}}" alt="Image" width="100%" >
Directive:
myApp.directive('checkImage', function($http) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
attrs.$observe('ngSrc', function(ngSrc) {
$http.get(ngSrc).success(function(){
alert('image exist');
}).error(function(){
alert('image not exist');
element.attr('src', '/images/default_user.jpg'); // set default image
});
});
}
};
});
See the demo http://jsfiddle.net/manzapanza/dtt1z5p8/
You can pass in an expression to the alt attribute:
<img alt="{{user_image}}" src="/images/profile_images/{{result.image}}">
Edit: Doh, misread your question. In your controller (or the service which provides the image to the controller), you can set result.image to the user's image or the default image.
Another option is to use conditional logic in ng-src:
<img ng-src="{{ result.image || 'default_user.jpg' }}"/>
This will set the src to result.image if it exists, otherwise to the default.
Personally, I like the first option better since it keeps logic out of the view, but I bet you could find someone who would argue that it belongs in the view since it is view logic.
Edit 2: here is a plnkr: http://plnkr.co/edit/vfYU8T3PlzjQPxAaC5bK?p=preview
The previously mentioned solutions will double the HTTP requests. Every image will be loaded twice!
This solution does not reload the image just to see if it exists:
myApp.directive('checkImage', function() {
return {
link: function(scope, element, attrs) {
element.bind('error', function() {
element.attr('src', '/images/default_user.jpg'); // set default image
});
}
}
});
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;
});
}
};
});
If you are using Ionic 2 you can try this, it's worked for me:
<img src="{{data.imgPath || 'assets/img/empty.png'}}" >
Just for fun ,
Angular 2 is on the way . According the specification ngIf may solve in any directive .
There is no need to complex directives . Build a class checks for the result object which have method check image is exists as #manzapanza did.
Build a class definition as follows :
<div *ngIf=result.isImageExists()>
<img [src]="result.image" />
</div>
<div *ngIf=!result.isImageExists()>
<img [src]="result.defaultImage" />
</div>
I think new angular is better and more clear.
Hope Helpes!!;
If you are working with Angular, probably you will have case where you want to call some JQUERY library to do something for you. The usual way would be to call JQUERY function on page ready:
$(function(){
$(element).someJqueryFunction();
});
If your element content is dynamically added using Angular, than this approach is not good as I understood, and you need to create an custom directive in order to fulfill this functionality.
In my case, I want to load bxSlider on <ul class="bxslider"> element, but content of the <ul> element should be loaded using angular ng-repeat directive.
Picture names are collected using REST service from the database.
I call my directive called startslider using the following code:
<div startslider></div>
My custom Angular directive is: (pictures is variable in the $scope passed from my controller, which called REST service)
application.directive('startslider', function () {
return {
restrict: 'A',
replace: false,
template: '<ul class="bxslider">' +
'<li ng-repeat="picture in pictures">' +
'<img ng-src="{{siteURL}}/slide/{{picture.gallery_source}}" alt="" />'
'</li>' +
'</ul>',
link: function (scope, elm, attrs) {//from angular documentation, the link: function should be called in order to change/update the dom elements
elm.ready(function () {
elm.bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
}
};
});
As result, I get all the pictures from the database displayed on my screen, but without bxSlider loaded (all pictures displayed one bellow other).
Please note that bxSlider is working, because when I call $(element).bxSlider(); on manually written code, the slider loads.
Why is this happening?
EDIT:
I think your problem is caused by trying to call the slider on HTMLUListElement, which is an object.
To call the slider on the DOM element, you could use $("." + $(elm[0]).attr('class')) that will use the existing bxslider class, or better assign an id to your <div startslider id="slideshow"></div> and call like $("." + $(elm[0]).attr('id'))
elm.ready(function() {
$("." + $(elm[0]).attr('class')).bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
Full code:
http://jsfiddle.net/z27fJ/
Not my solution, but I'd thought I would pass it along.
I like this solution the best (Utilizes directive controllers)
// slightly modified from jsfiddle
// bxSlider directive
controller: function() {},
link: function (scope, element, attrs, controller) {
controller.initialize = function() {
element.bxSlider(BX_SLIDER_OPTIONS);
};
}
// bxSliderItem directive
require: '^bxSlider',
link: function(scope, element, attrs, controller) {
if (scope.$last) {
controller.initialize();
}
}
http://jsfiddle.net/CaioToOn/Q5AcH/8/
Alternate, similar solution, using events (I do not like)
Jquery bxslider not working + Angular js ng-repeat issue
Root issue
You cannot call scope.$apply in the middle of a digest cycle.
You cannot fire bxSlider() until the template gets compiled.
Ultimately, you need to wait for the template to be compiled and available before calling bxSlider()
Calling a function when ng-repeat has finished
this is the directive
.directive('slideit', function () {
return function (scope, elm, attrs) {
var t = scope.$sliderData;
scope.$watch(attrs.slideit, function (t) {
var html = '';
for (var i = 0; i <= t.length-1; i++) {
html += '<li><ul class="BXslider"><li class="BXsliderHead"><img src="'+scope.$imageUrl+'flight/'+t[i].code+'.gif" />'+t[i].name+'</li><li class="BXsliderChild">'+t[i].noneStop+'</li><li class="BXsliderChild">'+t[i].oneStop+'</li><li class="BXsliderChild">'+t[i].twoStop+'</li></ul></li>';
}
angular.element(document).ready(function () {
$("#" + $(elm[0]).attr('id')).html(html).bxSlider({
pager:false,
minSlides: 3,
maxSlides: 7,
slideWidth: 110,
infiniteLoop: false,
hideControlOnEnd: true,
auto: false,
});
});
});
};
});
this is the html in view
<div ui-if="$sliderData">
<ul id="experimental" slideit="$sliderData"></ul>
</div>
and the important part is the dependency of js file
<script src="/yii-application/frontend/web/js/libraries/angular/angular.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/angular/angular-ui.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery/jquery-1.11.3.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/flex/jquery.bxslider.min.js"></script>
<script src="/yii-application/frontend/web/assets/e3dc96ac/js/bootstrap.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery-ui/jquery-ui.min.js"></script>
<script src="/yii-application/frontend/web/js/searchResult.js"></script>
<script src="/yii-application/frontend/web/js/Flight.js"></script>
and don't forget to put ui in module
var app = angular.module('myApp', ['ui']);
this is the bxslider that i use !!!! maybe solve your problem