AngularJS hide div after delay - angularjs

While creating my app in AngularJS (awesome framework!) I stuck in one task: how to show and hide hidden div (ng-show) after some action.
Detailed description: using AngularUI $modal service I'm asking if user wants to perform action, if yes, I run service to POST request via $http to send which item I want to delete. After it finished I want to show hidden div with information, that process has accomplished successfully. I created a simple service with $timeout to set div's ng-show and hide after some time but it doesn't update variable assigned to ng-show directive. Here is some code:
Controller for listing and deleting items
$scope.deleteSuccessInfo = false; //variable attached to div ng-show
$scope.deleteCluster = function(modalType, clusterToDelete) {
modalDialogSrvc.displayDialog(modalType)
.then(function(confirmed) {
if(!confirmed) {
return;
}
clusterDeleteSrvc.performDelete(itemToDelete)
.then(function(value) {
//my attempt to show and hide div with ng-show
$scope.deleteSuccessInfo = showAlertSrvc(4000);
updateView(itemToDelete.itemIndex);
}, function(reason) {
console.log('Error 2', reason);
});
}, function() {
console.info('Modal dismissed at: ' + new Date());
});
};
function updateView(item) {
return $scope.listItems.items.splice(item, 1);
}
Part of service for deleting item
function performDelete(itemToDelete) {
var deferred = $q.defer(),
path = globals.itemsListAPI + '/' + itemToDelete.itemId;
getDataSrvc.makeDeleteRequest(path)
.then(function() {
console.log('Item removed successfully');
deferred.resolve({finishedDeleting: true});
}, function(reason) {
console.log('error ', reason);
deferred.reject(reason);
});
return deferred.promise;
}
return {
performDelete: performDelete
};
Simple service with $timeout to change Boolean value after some time
angular.module('myApp')
.service('showAlertSrvc', ['$timeout', function($timeout) {
return function(delay) {
$timeout(function() {
return false;
}, delay);
return true;
};
}]);
I tried $scope.$watch('deleteSuccessInfo', function(a, b) {...}) with no effect. How to apply false after some delay? Or maybe You would achieve this in other way?
Thank You in advance for any help!

Change the implementation of the showAlertSrvc service, like this:
angular.module('myApp')
.service('showAlertSrvc', ['$timeout', function($timeout) {
return function(delay) {
var result = {hidden:true};
$timeout(function() {
result.hidden=false;
}, delay);
return result;
};
}]);
And then change thes 2 lines:
The declaration of deleteSuccessInfo should be like this:
$scope.deleteSuccessInfo = {};
And then do this:
$scope.deleteSuccessInfo = showAlertSrvc(4000);
And finally in your view do this "ng-show=!deleteSuccessInfo.hidden"
Example

Related

ng-if dependent element not disappearing

I've got the following problem:
When clicking a button my searchResults window opens due to 'displayResults' is set on 'true'. But when clicking on the close button it's not closed even if $scope.displayResults is set to false again.
html
<section id="searchResults" data-ng-if="displayResults">
<h2>Suchergebnisse:</h2>
<div id="closeMenuS">❌</div>
<ul data-ng-repeat="x in sItems">
....
</ul>
</section>
AngularJS
$http({
method : "POST",
url : "/searchFor",
data: {item: sI, dID: searchID}
}).then(function(response){
if(response.data.length > 0 || response.data != 'undefined'){
$("#sidebar").css({pointerEvents: "none"});
$("#default").css({pointerEvents: "none"});
$scope.displayResults = true;
$scope.sItems = [];
for(let i = 0; i < response.data.length; i++){
$scope.sItems[i] = response.data[i];
}
window.setTimeout(function(){
$("#closeMenuS").click(function(){
$scope.displayResults = false;
$("#sidebar").css({pointerEvents: "auto"});
$("#default").css({pointerEvents: "auto"});
});
}, 30);
}
}, function myError(response) {
$scope.myWelcome = response.statusText;
});
If you're in a callback in a jQuery listener, you need to explicitly apply the change. Try this:
window.setTimeout(function(){
$("#closeMenuS").click(function(){
$scope.displayResults = false;
$scope.$apply();
$("#sidebar").css({pointerEvents: "auto"});
$("#default").css({pointerEvents: "auto"});
});
}, 30);
AngularJs has its own implementation for timeouts. By using the native setTimeout() you are hiding what you are trying to do from angular.
The result is that AngularJs has no idea that your $scope changed.
The correct way would be to inject the $timeout service and replace your setTimeout() call with $timeout (I stripped down the rest of the code for simplification):
angular.module("YourModule")
.controller("YourController", ["$scope", "$timeout", YourController]);
function YourController($scope, $timeout)
{
...
$scope.doSomeMagic = function()
{
$timeout(function()
{
$("#closeMenuS").click(function()
{
$scope.displayResults = false;
$("#sidebar").css({pointerEvents: "auto"});
$("#default").css({pointerEvents: "auto"});
});
}, 30);
}
...
}
This is why the $apply() function suggested by Itamar works. You're basically forcing AngularJs to detect changes.
However, the better practice is to use the tools provided by AngularJs and avoid native javascript when possible.

materialize Dropdwon in Angular js after ajax call

I have a master page and child page in MVC
All Js files are included in master page.Now in child page I have two dropdowns which gets bind with ajax returned data.
I can see data are being populated in select options but materialize css not created.
HTML
<select data-ng-init="getAllItems()" ng-model="Item[0]" ng-options="Item['title'] for Item in Items track by Item['id']">
AJAX
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
}
});
}
I have used
$('select').material_select()
in a js file that is included on master page at the end,
So my thinking is the JS where I am using $('select').material_select() gets loaded before dropdown populations, but I have included it at the end,
I Manage to get it worked
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select()
}
});
}
$scope.$apply($scope.getAllItems ());
but on console I am getting error
[$rootScope:inprog]
any suggestion.
Thanks
So, you have two ways:
1) It is put your select initialization in then fuction. Not the good way
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select();
$scope.$apply();
}
});
}
2) Make a directive that wraps .material_select(). Better way
.directive('materialSelect', [function(){
return {
restrict: 'E',
scope: {
items: '='
},
link: function(scope, elem attrs) {
elem.material_select()
}
}
}])
<material-select items="items"></material-select>
UPDATE
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select()
$scope.$apply()
}
});
}
$scope.getAllItems ();

triggerHandler causing Error: [$rootScope:inprog] $apply already in progress error - AngularJS

I am trying to trigger the click of a button when a key is pressed. I'm doing this using the triggerHandler function, but this is causing the error above. I'm thinking I must have created some kind of circular reference/loop, but I can't see where.
This is my HTML:
<button id="demoBtn1" hot-key-button hot-key="hotKeys.primaryTest" class="po-btn primary-btn" type="submit" ng-click="btnFunction()"></button>
Here's my controller:
.controller('BtnCtrl', function ($scope) {
$scope.checkKeyPress = function ($event, hotKeyObj) {
for(var key in hotKeyObj) {
if($event.keyCode == hotKeyObj[key].keyCode) {
var id = hotKeyObj[key].btnId;
hotKeyObj[key].funcTriggered(id);
}
}
}
$scope.clickFunction = function(id) {
var currentButton = angular.element(document.getElementById(id));
currentButton.triggerHandler("click");
}
$scope.btnFunction = function() {
console.log("clickFunction1 has been triggered");
}
$scope.hotKeys = {
'primaryTest': {
keyCode: 49,
keyShortcut: "1",
label: "Button",
btnId: 'demoBtn1',
funcTriggered: $scope.clickFunction
},
// more objects here
}
}
})
And my directive is here:
.directive("hotKeyButton", function() {
return {
controller: 'BtnCtrl',
scope: {
hotKey: '='
},
transclude: true,
template: "<div class='key-shortcut'>{{hotKey.keyShortcut}}</div><div class='hotkey-label'>{{hotKey.label}}</div>"
};
})
It's a bit of a work in progress, so I suspect there might be small errors in places, but I'm primarily interested in the logic running from the keypress to btnFunction being triggered. The error fires on the currentButton.triggerHandler("click") line.
Can anyone see what I've done? Thanks.
Since you have a problem with $apply in progress - you can just wrap your triggerHandler call into $timeout wrapper - just to make everything you need in another $digest-cycle, like this:
$scope.clickFunction = function(id) {
var currentButton = angular.element(document.getElementById(id));
$timeout(function () {
currentButton.triggerHandler("click");
});
}
After this everything will work OK.
Also don't forget to $inject $timeout service into your BtnCtrl.
Also i'm not sure you need to define controller property for your directive, but it's not a main case.
Demo: http://plnkr.co/edit/vO8MKAQ4397uqqcAFN1D?p=preview

Protractor select values from drop down with promise

I'm running a protractor tests and filling in a form with dropdowns. How can I ensure that each dropdown has a promise that is resolved before moving onto the next one? Right now I'm using sleeps and know that can't be the right way.
Describe('Filling site utility form', function() {
beforeEach(function(){
var input_monthly = by.linkText('Input Monthly');
browser.wait(function() {
return browser.isElementPresent(input_monthly);
});
var field1 = element(by.id('field1'));
Test.dropdown(field1, 1);
var field2 = element(by.id('field2'));
Test.dropdown(field2, 1);
});
My dropdown function is:
dropdown = function(element, index, milliseconds) {
element.findElements(by.tagName('option'))
.then(function(options) {
options[index].click();
});
if (typeof milliseconds !== 'undefined') {
browser.sleep(milliseconds);
}
}
Not 100% sure what you mean but I suspect what you're interested in is this:
describe('Filling site utility form', function() {
beforeEach(function(){
var input_monthly = by.linkText('Input Monthly');
browser.wait(function() {
return browser.isElementPresent(input_monthly);
});
var promise1 = element(by.id('field1')).all(by.tagName('option')).get(1).click();
var promise2 = element(by.id('field2')).all(by.tagName('option')).get(1).click();
protractor.promise.all([promise1, promise2]).then(function() {
//what do you want to do next?
});
});
});

Load Angular Directive Template Async

I want to be able to load the directive's template from a promise. e.g.
template: templateRepo.get('myTemplate')
templateRepo.get returns a promise, that when resolved has the content of the template in a string.
Any ideas?
You could load your html inside your directive apply it to your element and compile.
.directive('myDirective', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//Some arbitrary promise.
fetchHtml()
.then(function(result){
element.html(result);
$compile(element.contents())(scope);
}, function(error){
});
}
}
});
This is really interesting question with several answers of different complexity. As others have already suggested, you can put loading image inside directive and when template is loaded it'll be replaced.
Seeing as you want more generic loading indicator solution that should be suitable for other things, I propose to:
Create generic service to control indicator with.
Manually load template inside link function, show indicator on request send and hide on response.
Here's very simplified example you can start with:
<button ng-click="more()">more</button>
<div test="item" ng-repeat="item in items"></div>
.throbber {
position: absolute;
top: calc(50% - 16px);
left: calc(50% - 16px);
}
angular
.module("app", [])
.run(function ($rootScope) {
$rootScope.items = ["One", "Two"];
$rootScope.more = function () {
$rootScope.items.push(Math.random());
};
})
.factory("throbber", function () {
var visible = false;
var throbber = document.createElement("img");
throbber.src = "http://upload.wikimedia.org/wikipedia/en/2/29/Throbber-Loadinfo-292929-ffffff.gif";
throbber.classList.add("throbber");
function show () {
document.body.appendChild(throbber);
}
function hide () {
document.body.removeChild(throbber);
}
return {
show: show,
hide: hide
};
})
.directive("test", function ($templateCache, $timeout, $compile, $q, throbber) {
var template = "<div>{{text}}</div>";
var templateUrl = "templateUrl";
return {
link: function (scope, el, attr) {
var tmpl = $templateCache.get(templateUrl);
if (!tmpl) {
throbber.show();
tmpl = $timeout(function () {
return template;
}, 1000);
}
$q.when(tmpl).then(function (value) {
$templateCache.put(templateUrl, value);
el.html(value);
$compile(el.contents())(scope);
throbber.hide();
});
},
scope: {
text: "=test"
}
};
});
JSBin example.
In live code you'll have to replace $timeout with $http.get(templateUrl), I've used the former to illustrate async loading.
How template loading works in my example:
Check if there's our template in $templateCache.
If no, fetch it from URL and show indicator.
Manually put template inside element and [$compile][2] it.
Hide indicator.
If you wonder what $templateCache is, read the docs. AngularJS uses it with templateUrl by default, so I did the same.
Template loading can probably be moved to decorator, but I lack relevant experience here. This would separate concerns even further, since directives don't need to know about indicator, and get rid of boilerplate code.
I've also added ng-repeat and run stuff to demonstrate that template doesn't trigger indicator if it was already loaded.
What I would do is to add an ng-include in my directive to selectively load what I need
Check this demo from angular page. It may help:
http://docs.angularjs.org/api/ng.directive:ngInclude
````
/**
* async load template
* eg :
* <div class="ui-header">
* {{data.name}}
* <ng-transclude></ng-transclude>
* </div>
*/
Spa.Service.factory("RequireTpl", [
'$q',
'$templateCache',
'DataRequest',
'TplConfig',
function(
$q,
$templateCache,
DataRequest,
TplConfig
) {
function getTemplate(tplName) {
var name = TplConfig[tplName];
var tpl = "";
if(!name) {
return $q.reject(tpl);
} else {
tpl = $templateCache.get(name) || "";
}
if(!!tpl) {
return $q.resolve(tpl);
}
//加载还未获得的模板
return new $q(function(resolve, reject) {
DataRequest.get({
url : "/template/",
action : "components",
responseType : "text",
components : name
}).success(function(tpl) {
$templateCache.put(name, tpl);
resolve(tpl);
}).error(function() {
reject(null);
});
});
}
return getTemplate;
}]);
/**
* usage:
* <component template="table" data="info">
* <span>{{info.name}}{{name}}</span>
* </component>
*/
Spa.Directive.directive("component", [
"$compile",
"RequireTpl",
function(
$compile,
RequireTpl
) {
var directive = {
restrict : 'E',
scope : {
data : '='
},
transclude : true,
link: function ($scope, element, attrs, $controller, $transclude) {
var linkFn = $compile(element.contents());
element.empty();
var tpl = attrs.template || "";
RequireTpl(tpl)
.then(function(rs) {
var tplElem = angular.element(rs);
element.replaceWith(tplElem);
$transclude(function(clone, transcludedScope) {
if(clone.length) {
tplElem.find("ng-transclude").replaceWith(clone);
linkFn($scope);
} else {
transcludedScope.$destroy()
}
$compile(tplElem.contents())($scope);
}, null, "");
})
.catch(function() {
element.remove();
console.log("%c component tpl isn't exist : " + tpl, "color:red")
});
}
};
return directive;
}]);
````

Resources