Custom directive for preloading background image not working AngularJS - angularjs

I am working on a custom directive to preload images while the state (with ui-router is changing) so that both data and content are resolved before navigating to the new state.
I have created a Pen here: https://codepen.io/wons88/pen/NgbNvO to show my basic implementation with the bgSrc directive:
app.directive('bgSrc', ['preloadService', function (preloadService) {
return function (scope, element, attrs) {
element.hide();
preloadService(attrs.bgSrc).then(function () {
console.log(element.css);
element.css({
"background-image": "url('" + attrs.bgSrc + "')"
});
element.fadeIn();
});
}
}]);
and the preloadService:
app.factory('preloadService', ['$q', '$rootScope', function ($q, $rootScope) {
return function (url) {
var deffered = $q.defer(),
image = new Image();
image.src = url;
if (image.complete) {
$rootScope.loading = false;
deffered.resolve();
} else {
$rootScope.loading = true;
image.addEventListener('load',
function () {
deffered.resolve();
$rootScope.loading = false;
});
image.addEventListener('error',
function () {
deffered.reject();
$rootScope.loading = true;
});
}
return deffered.promise;
};
}]);
And the HTML:
<div bg-src="https://images5.alphacoders.com/446/thumb-1920-446028.jpg">Background img should show up...</div>
EDIT:
The problem is that the image is never shown even if it is loaded (as shown in the network tab in chrome dev tools). If I make it statically applying style (or class) the image is shown no problem, just loads slower... hence trying to apply the directive.
I know there is something wrong happening in the bgSrc directive but I am unable to pinpoint it. Any help on the matter would be much appreciated :)

element.hide is not a function
You are calling
element.hide();
in your link function, but jQlite does not have a hide() method. Since you did not include jQuery in your Codepen, it's failing on that line.
https://docs.angularjs.org/api/ng/function/angular.element
Instead, use css
element.css('visibility', 'invisible');
Or add jQuery

Related

Dropzone becomes inactive when used in AngularJS ng-view

I have a controller that displays images via a template that is passed through ng-view and runs smoothly. I also have dropzone up and running and working smoothly. However, as soon as I add dropzone to the template the dropzone is no longer active. It displays (the zone isn't clickable unless I manually add dz-clickable, but still does nothing).
I could add the dropzone in a ng-include above the area and have it hidden when the template is looking elsewhere but have heard that ng-include uses more processor than ng-view so would prefer to keep it all together.
I have seen that there is a dropzone angularjs directive but have not managed to merge it successfully with my controller that gets placed into the ng-view or if that would even be successful?
Here is the directive:
(function(){
angular.module('dropZone', [])
.directive('dropZone', function() {
return function(scope, element, attrs) {
element.dropzone({
url: "/upload",
maxFilesize: 100,
paramName: "uploadfile",
maxThumbnailFilesize: 5,
init: function() {
scope.files.push({file: 'added'}); // here works
this.on('success', function(file, json) {
});
this.on('addedfile', function(file) {
scope.$apply(function(){
alert(file);
scope.files.push({file: 'added'});
});
});
this.on('drop', function(file) {
alert('file');
});
}
});
}
});
}());
and here is my current controller:
(function() {
'use strict';
angular
.module('app')
.controller('CustomController', CustomController);
CustomController.$inject = ['api'];
function CustomController(api) {
var vm = this;
api.getDesigns()
.then(function(data) {
vm.designs = data;
});
}
}());
Found my own answer. Rather than adding the dropzone hard coded into the template I just programmatically added the dropzone within the controller scope function using :
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});

angularjs binding in scope not in ui

i am using jquery fancytree plug in in my angularjs
and i am trying to use the 'activate' event to call a function inside the $scope.
if i breakpoint the code - it looks good , but its not showing in the html.
here is my code:
app.controller('ctrl', ['$scope', 'treeSvc', function ($scope, treeSvc) {
$scope.selectedNode = null;
$scope.SetSelectedNode = function(newNode){
$scope.selectedNode = newNode;
//a break point here shows that $scope.selectedNode is really changing but its not showing in the html. calling this function outside the fancy tree - works.
}
treeSvc.get()
.then(function (response) {
$("#tree").fancytree({
source: response.data,
activate: function(event, data) {
$scope.SetSelectedNode(data.node);
}
});
},
function () {
alert('error getting tree data');
});
}]);
Html
{{selectedNode | json}}
any ideas why?
As your event is fired "outside" of angular, you need to refresh the scope manually :
$("#tree").fancytree({
source: response.data,
activate: function(event, data) {
$scope.SetSelectedNode(data.node);
$scope.$apply();
}
});
See here for more infos : https://docs.angularjs.org/guide/scope

Angularjs: controller init function avoid init to be called

I have the following controller code:
applicationControllers.controller('PostsController', ['$scope', '$http', function
($scope, $http) {
var page = 1;
$scope.init = function() {
this.loadPage(page);
}
$scope.nextPage = function() {
page++;
this.loadPage(page);
}
$scope.previousPage = function() {
page--;
if (page <= 0) { page = 1 }
this.loadPage(page);
}
$scope.filterByProvince = function(provinceName) {
console.log(provinceName);
}
$scope.loadPage = function(page) {
$http.get('/posts.json?page=' + page).success(function(data) {
$scope.posts = data;
});
}
$scope.init();
}]);
The problem is when using a ng-click directive to filterByProvince('test'), it seems the init function are also called. I want to avoid this behaviour.
Any help?
In Angular, when you want to have an <a> element display like an <a> element, but used differently (e.g. as a button (by attaching some ngClick directive)), you should use an empty href:
Do something
The problem with using href="#" is that it is eihter:
Recognized by ngRoute (if it is used) as a "link" to the home route ('/'), so even if you are in that route it gets reloaded causing the effects of the ngClick callback to be voided or
Recognized by the browser as link with an anchor tag to the same page, so clicking it causes the page to reload (again voiding any effect of the ngClick callback).
I am not sure how ui-router handles this, but I am not a big fan of ui-router - maybe someone with more experience can give some feedback.

AngularJs event to call after content is loaded

I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like
document.addEventListener('DOMContentLoaded', function () {
//Content goes here
}, false);
Above call doesn't work for me in AngularJs controller.
According to documentation of $viewContentLoaded, it supposed to work
Emitted every time the ngView content is reloaded.
$viewContentLoaded event is emitted that means to receive this event you need a parent controller like
<div ng-controller="MainCtrl">
<div ng-view></div>
</div>
From MainCtrl you can listen the event
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
Check the Demo
Angular < 1.6.X
angular.element(document).ready(function () {
console.log('page loading completed');
});
Angular >= 1.6.X
angular.element(function () {
console.log('page loading completed');
});
fixed - 2015.06.09
Use a directive and the angular element ready method like so:
js
.directive( 'elemReady', function( $parse ) {
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
})
}
}
})
html
<div elem-ready="someMethod()"></div>
or for those using controller-as syntax...
<div elem-ready="vm.someMethod()"></div>
The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.
You may need to prioritize this directive in case you have other directives operating on the same node.
You can directly call it by adding {{YourFunction()}} after HTML element.
Here is a Plunker Link.
I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.
<body>
-- some html here --
--and at the end or where ever you want --
<div ng-init="FunCall()"></div>
</body>
and in that function simply call your logic.
$scope.FunCall = function () {
alert("Called");
}
var myM = angular.module('data-module');
myM.directive('myDirect',['$document', function( $document ){
function link( scope , element , attrs ){
element.ready( function(){
} );
scope.$on( '$viewContentLoaded' , function(){
console.log(" ===> Called on View Load ") ;
} );
}
return {
link: link
};
}] );
Above method worked for me
you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.
download ng-load for angular js
Following is example for iframe, once it is loaded testCallbackFunction will be called in controller
EXAMPLE
JS
// include the `ngLoad` module
var app = angular.module('myApp', ['ngLoad']);
app.controller('myCtrl', function($scope) {
$scope.testCallbackFunction = function() {
//TODO : Things to do once Element is loaded
};
});
HTML
<div ng-app='myApp' ng-controller='myCtrl'>
<iframe src="test.html" ng-load callback="testCallbackFunction()">
</div>
If you're getting a $digest already in progress error, this might help:
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
if(!$scope.$$phase) {
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
}
else {
var func = $parse(attrs.elemReady);
func($scope);
}
})
}
}
I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.
angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;
self.controllerFunction = function () { alert('controller function');}
$timeout(function () {
var vanillaFunction = function () { alert('vanilla function'); }();
self.controllerFunction();
});
}]);
Running after the page load should partially be satisfied by setting an event listener to the window load event
window.addEventListener("load",function()...)
Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.
You can broadcast and emit events for communications bridges.
For example:
module set onload event and build logic
module broadcast event to controllers when logic required it
controllers will listen and execute their own logic based on module onload processes.
If you want certain element to completely loaded, Use ng-init on that element .
e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>
the initModalFacultyInfo() function should exist in the controller.
I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):
// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
$window.prerenderReady = true; // Raise the flag saying we are ready
} else {
if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
$timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
}
};
$rootScope.$on('$stateChangeSuccess', function() {
checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
viewContentLoads ++;
});
var myTestApp = angular.module("myTestApp", []);
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
alert("is called on page load.");
};
});
The solution that work for me is the following
app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
if (!!attr.onFinishRender) {
$parse(attr.onFinishRender)(scope);
}
});
}
if (!!attr.onStartRender) {
if (scope.$first === true) {
$timeout(function () {
scope.$emit('ngRepeatStarted');
if (!!attr.onStartRender) {
$parse(attr.onStartRender)(scope);
}
});
}
}
}
}
}]);
Controller code is the following
$scope.crearTooltip = function () {
$('[data-toggle="popover"]').popover();
}
Html code is the following
<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.
var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
src = $audio.attr('src');
if(src != undefined){
window.clearInterval(a);
$('audio').mediaelementplayer({
audioWidth: '100%'
});
}
}, 0);

asynchronous loading controller's code

how do you bootstrap a controller that loaded asynchronously via require.js?
if I have something like that:
$routeProvider.when('/',
{
templateUrl:'view1.html',
controller:'ctrl',
resolve:{
load:function($q){
var dfrd = $q.defer();
require(['view1-script'],function(){
dfrd.resolve();
})
return dfrd.promise;
}
}
})
why angular still won't find the controller? I am resolving the route after it loads the script
check out this plunkr
try calling $controllerProvider.register to create your controller. I would also call $apply() on the $rootScope after resolving the deferred because without it, the view does not seem to appear:
load: function($q, $rootScope){
var dfrd = $q.defer();
require(['view1'],function(){
dfrd.resolve();
$rootScope.$apply();
})
return dfrd.promise;
}
http://plnkr.co/edit/fe2Q3BhxPYnPmeiOORHP
in addition, here is a good post: http://weblogs.asp.net/dwahlin/archive/2013/05/22/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs.aspx
It's been 3 years, but just in case anyone still interested, a few months ago I wrote a post about a similar technique to do it.
The most important part is that second parameter of the method $routeProvider.when(route, ctrl) method can handle promises, so you can simply emulate it:
function controllerFactory(ctrl) {
return {
then: function (done) {
var self = this;
require(['./controller/' + ctrl], function (ctrl) {
self.controller = ctrl;
self.resolve = ctrl.resolve;
self.templateUrl = ctrl.templateUrl;
done();
});
}
};
}
And you can end up writing your route definition like this:
$routeProvider.
when('/some/route', controllerFactory('some/route')).
when('/other/route', controllerFactory('other/route'))

Resources