How to hide modal window on $route.reload (using angularjs-ui)? - angularjs

I have a form in a modal window which adds data to a database. Upon successful addition, I am calling the $route.reload() function to refresh the underlying page, however, my modal window is still shown.
$scope.add = function() {
$http.post('/api/contact', $scope.form).
success(function(data) {
$scope.showModal = false;
$route.reload();
});
};
The modal is the standard angularjs-ui modal:
<div modal="showModal" close="close()" options="opts"> etc </div>
I tried to add $scope.showModal = false; at the beginning of my controller but that didn't help either.
Here's a code in plnkr:
http://plnkr.co/edit/xz6gJRTBisVCeM0gNLx6?p=preview
Note that I am aware that if you comment out the $route.reload() option the items in the list get updated, however in my app I am writing things into a database so I'd need the $route.reload as I need to re-query the DB.

You need a timeout before applying the $route.reload() because that's trying to fire off before the browser has time to update the view. This works: http://plnkr.co/edit/E7oraQ?p=preview
Also, unless you're dealing with non-angular stuff, there isn't any reason to reload just to write to the DB. You should be able to re-query within the controller.

Related

Angular hide dirrective depending on action in another directive

I'm new in angular and i'm looking for the best way to do what I want.
In my main page I have 2 directives, one is used to display a button (and maybe other stuff). And another used to display a kind of dialog box/menu.
Each directive has its own controller.
I want to show or hide the second directive when I click on the button in the first one.
I don't really know what are goods or wrong approaches. Should I use a service injected in both controller and set a variable with ng-show in the second directive? This solution doesn't really hide the directive because I need a div inside the directive to hide its content and isn't too much to use a service only for one boolean?
Should I use a kind of global variable (rootscope?) or inject the first controller inside the second one?
Or maybe use a third controller in my main page (used with a service?) or use only one controller for both directive?
Basically without directive I would probably used only one main controller for my whole page and set a variable.
In fact the first directive is just a kind of button used to display "something", and the second directive just a kind of popup waiting a boolean to be displayed. That's why I finally used a service containing a boolean with a getter and a setter to avoid any interaction beetween both controller.
My both controller use this service, the first one to set the value when we click on the element and the second controller provide just a visibility on the getter for my ng-show.
I don't know if it is the best way to do but I am satisfied for now.
Small example here (without directive but with same logic) :
http://codepen.io/dufaux/pen/dXMrPm
angular.module('myModule', []);
angular.module("myModule")
.controller("ButtonCtrl", buttonCtrl)
.controller("PopUpCtrl", popUpCtrl)
.service("DisplayerService", displayerService);
//ButtonCtrl
buttonCtrl.$inject = ["DisplayerService", "$scope"];
function buttonCtrl(DisplayerService, $scope) {
var vm = this;
vm.display = function(){
DisplayerService.setDisplay(!DisplayerService.getDisplay());
}
}
//PopUpCtrl
popUpCtrl.$inject = ["DisplayerService"];
function popUpCtrl(DisplayerService) {
var vm = this;
vm.displayable = function(){
return DisplayerService.getDisplay();
}
}
//Service
function displayerService(){
var vm = this;
vm.display = false;
vm.setDisplay = function(value){
vm.display = value;
}
vm.getDisplay = function(){
return vm.display;
}
}
--
<body data-ng-app="myModule">
<div data-ng-controller="ButtonCtrl as btnCtrl" >
<button data-ng-click="btnCtrl.display()">
display
</button>
</div>
[...]
<div data-ng-controller="PopUpCtrl as popUpCtrl" >
<div data-ng-show="popUpCtrl.displayable()">
hello world
</div>
</div>
</body>

How to trigger an event in one view from a different view?

I am trying to open an Angular accordian in the header.html by clicking a button which is in the body.html. Essentially triggering an event in one view from a completely different view. Does anyone have any idea how to do this in Angular?
What you can do is using events to let your accordion directive know that something happend or use a shared service. Considering the performance, it does not make a huge difference, but only if you use $emit instead of $broadcast since the event fired via $emit bubbles up your scope hierarchy and $broadcast sends the event down. Also make sure to fire the event on the $rootScope, so it won't event bubble up anymore.
So you in case you want to use events for you could have a method on your component that fires the event via $emit on the $rootScope as follows:
function openAccordion() {
$rootScope.$emit('on-accordion-open', null);
}
You could then use this in your view, e.g. in body.html. Remember that function above is part of another directive / component or controller.
<button ng-click="vm.openAccordion()">Open Accordion</button>
Also note that I assume you are using controllerAs syntax (set to vm).
In your accordion directive you can then hook up listeners to several events for example the on-accordion-open:
$rootScope.$on('on-accordion-open', function() {
// Open the accordion
});
The other soltuion is to use a shared service. In this case I would create a AccordionServce that is aware of all instances of accordions. The service could look like this:
angular.module('myApp').service('AccordionService', function() {
var accordions = {};
this.addAccordion = function(name, accordion) {
accordions[name] = accordion;
};
this.removeAccordion = function(name) {
delete accordions[name];
};
this.getAccordion = function(name) {
return accordions[name];
};
});
In your accordion's controller you then add the accordion to the AccordionService via
accordionService.addAccordion('myAccordion', this);
The this in the snippet above is refering to the accordion controller. Thats important because if you then get an accordion in your component in the body.html, you'll get the controller instance and can call methods like open.
So in your body component you can then inject the AccordionService and get the accordion to call a method:
accordionService.getAccordion('myAccordion').open();
Make sure to define open on the accordion's controller.

ng-show directive takes too long to update the dom after trigger

The app has a controller, that uses a service to create an instance of video player. The video player triggers events to show progress every few seconds. When the video reaches to a certain point, I want to show a widget on top of the video player.
The view has the widget wrapped in ng-show directive.
It takes more then 60 seconds for the dom element to receive the signal to remove the ng-hide class after the event has been triggered and the values have been populated.
If I try to implement this using the plain dom menthod (like document.getElementById(eleId).innerHTML = newHTML), the update is instant.
What am I doing wrong? Here is the complete sequence in code:
Controller:
MyApp.controller('SectionController', ['$scope', 'PlayerService'], function($scope, PlayerService){
$scope.createPlayer = function() {
PlayerService.createPlayer($scope, wrapperId);
}});
Service:
MyApp.service('PlayerService', [], function(){
this.createPlayer=function(controllerScope, playerWrapper){
PLAYER_SCRIPT.create(playerWrapper) {
wrapper : playerWrapper,
otherParam : value,
onCreate : function(player) {
player.subscribe(PLAY_TIME_CHANGE, function(duration){
showWidget(controllerScope, duration);
})
}
}
}
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
}
}});
View:
<div ng-show="showWidget"> <div class="wdgt">{{widgetData.stuff}}</div> </div>
Solved it! $scope.$apply() did the trick.
My guess is, due to other complex logic ad bindings inside the app, there was a delay in computing the change by angular the default way.
#floribon Thanks for the subtle hint about "complex angular stuff".
The code inside the service function changed to:
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
$rootScope.$apply();
}}
Do you have complex angular stuff within your hidden view?
You should try to use ng-if instead of ng-show, the difference being that when the condition is false, ng-if will remove the element from the DOM instead of just hidding it (which is also what you do in vanilla JS).
When the view is simply hidden using ng-show however, all the watchers and bindings within it keep being computed by Angular. Let us know if ng-if solve your problem, otherwise I'll edit my answer.

Apply loading spinner during ui-router resolve

resolve property of $routeProvider allows to execute some jobs BEFORE corresponding view is rendered.
What if I want to display a spinner while those jobs are executed in order to increase user experience?
Indeed, otherwise the user would feel the application has been blocked since no view elements were displayed for some milliseconds > 600 for instance.
Of course, there was the way to define a global div element out of the current view to display in order to display the spinner thanks to the $scope.$rootChangeStart function.
But I don't want to hide the whole page with just a poor spinner in the middle.
I want some pages of my webapp differ regarding the way the loading is displayed.
I came across this interesting post containing the exact issue I described above:
That approach results in a horrible UI experience. The user clicks on
a button to refresh a list or something, and the entire screen gets
blanketed in a generic spinner because the library has no way of
showing a spinner just for the view(s) that are actually affected by
the state change. No thanks.
In any case, after I filed this issue, I realised that the "resolve"
feature is an anti-pattern. It waits for all the promises to resolve
then animates the state change. This is completely wrong - you want
your transition animations between states to run parallel to your data
loads, so that the latter can be covered up by the former.
For example, imagine your have a list of items, and clicking on one of
them hides the list and shows the item's details in a different view.
If we have an async load for the item details that takes, on average,
400ms, then we can cover up the load almost entirely in most cases by
having a 300ms "leave" animation on the list view, and a 300ms "enter"
animation on the item details view. That way we provide a slicker feel
to the UI and can avoid showing a spinner at all in most cases.
However, this requires that we initiate the async load and the state
change animation at the same moment. If we use "resolve", then the
entire async animation happens before the animation starts. The user
clicks, sees a spinner, then sees the transition animation. The whole
state change will take ~1000ms, which is too slow.
"Resolve" could be a useful way to cache dependencies between
different views if it had the option not to wait on promises, but the
current behaviour, of always resolving them before the state change
starts makes it almost useless, IMO. It should be avoided for any
dependencies that involve async loads.
Should I really stop using resolve to load some data and rather start loading them in the corresponding controller directly? So that I can update the corresponding view as long as the job is executed and in the place I want in the view, not globally.
You can use a directive that listens on $routeChangeStart and for example shows the element when it fires:
app.directive('showDuringResolve', function($rootScope) {
return {
link: function(scope, element) {
element.addClass('ng-hide');
var unregister = $rootScope.$on('$routeChangeStart', function() {
element.removeClass('ng-hide');
});
scope.$on('$destroy', unregister);
}
};
});
Then you place it on the specific view's loader, for example:
View 1:
<div show-during-resolve class="alert alert-info">
<strong>Loading.</strong>
Please hold.
</div>
View 2:
<span show-during-resolve class="glyphicon glyphicon-refresh"></span>
The problem with this solution (and many other solutions for that matter) is that if you browse to one of the routes from an external site there will be no previous ng-view template loaded, so your page might just be blank during resolve.
This can be solved by creating a directive that will act as a fallback-loader. It will listen for $routeChangeStart and show a loader only if there is no previous route.
A basic example:
app.directive('resolveLoader', function($rootScope, $timeout) {
return {
restrict: 'E',
replace: true,
template: '<div class="alert alert-success ng-hide"><strong>Welcome!</strong> Content is loading, please hold.</div>',
link: function(scope, element) {
$rootScope.$on('$routeChangeStart', function(event, currentRoute, previousRoute) {
if (previousRoute) return;
$timeout(function() {
element.removeClass('ng-hide');
});
});
$rootScope.$on('$routeChangeSuccess', function() {
element.addClass('ng-hide');
});
}
};
});
The fallback loader would be placed outside the element with ng-view:
<body>
<resolve-loader></resolve-loader>
<div ng-view class="fadein"></div>
</body>
Demo of it all: http://plnkr.co/edit/7clxvUtuDBKfNmUJdbL3?p=preview
i think this is pretty neat
app.run(['$rootScope', '$state',function($rootScope, $state){
$rootScope.$on('$stateChangeStart',function(){
$rootScope.stateIsLoading = true;
});
$rootScope.$on('$stateChangeSuccess',function(){
$rootScope.stateIsLoading = false;
});
}]);
and then on view
<div ng-show='stateIsLoading'>
<strong>Loading.</strong>
</div>
To further Pranay's answer this is how I did it.
JS:
app.run(['$rootScope',function($rootScope){
$rootScope.stateIsLoading = false;
$rootScope.$on('$routeChangeStart', function() {
$rootScope.stateIsLoading = true;
});
$rootScope.$on('$routeChangeSuccess', function() {
$rootScope.stateIsLoading = false;
});
$rootScope.$on('$routeChangeError', function() {
//catch error
});
}]);
HTML
<section ng-if="!stateIsLoading" ng-view></section>
<section ng-if="stateIsLoading">Loading...</section>
I'm two years late to this, and yes these other solutions work but I find it easier to just handle all this in a just a run block like so
.run(['$rootScope','$ionicLoading', function ($rootScope,$ionicLoading){
$rootScope.$on('loading:show', function () {
$ionicLoading.show({
template:'Please wait..'
})
});
$rootScope.$on('loading:hide', function () {
$ionicLoading.hide();
});
$rootScope.$on('$stateChangeStart', function () {
console.log('please wait...');
$rootScope.$broadcast('loading:show');
});
$rootScope.$on('$stateChangeSuccess', function () {
console.log('done');
$rootScope.$broadcast('loading:hide');
});
}])
You don't need anything else. Pretty easy huh. Here's an example of it in action.
In ui-router 1.0 $stateChange* events are deprecated. Use transition hook instead. See migration guide below for more details.
https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events
The problem with '$stateChangeStart' and '$stateChangeSuccess' is "$rootScope.stateIsLoading" doesn't get refreshed when you go back to last state.
Is there any solution on that?
I also used:
$rootScope.$on('$viewContentLoading',
function(event){....});
and
$rootScope.$on('$viewContentLoaded',
function(event){....});
but there is the same issue.

Hide angular-ui tooltip on custom event

I've been looking around and trying out different things but can't figure it out. Is it possible to hide an angular-ui tooltip with a certain event?
What I want to do is to show a tooltip when someone hovers over a div and close it when a users clicks on it because I will show another popup. I tried it with custom trigger events but can't seem to get it working. I made this:
<div ng-app="someApp" ng-controller="MainCtrl" class="likes" tooltip="show favorites" tooltip-trigger="{{{true: 'mouseenter', false: 'hideonclick'}[showTooltip]}}" ng-click="doSomething()">{{likes}}</div>
var app = angular.module('someApp', ['ui.bootstrap']);
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'mouseenter': 'mouseleave',
'click': 'click',
'focus': 'blur',
'hideonclick': 'click'
});
}]);
app.controller('MainCtrl', function ($scope) {
$scope.showTooltip = true;
$scope.likes = 999;
$scope.doSomething = function(){
//hide the tooltip
$scope.showTooltip = false;
};
})
http://jsfiddle.net/3ywMd/
The tooltip has to close on first click and not the 2nd. Any idea how to close the tooltip if user clicks on div?
I tried #shidhin-cr's suggestion of setting $scope.tt_isOpen = false but it had the rather significant issue that, while the tooltip does fade out, it is still present in the DOM (and handling pointer events!). So even though they can't see it, the tooltip can prevent users from interacting with content that was previously behind the tooltip.
A better way that I found was to simply trigger the event used as tooltip-trigger on the tooltip target. So, for example, if you've got a button that's a tooltip target, and triggers on click...
<button id="myButton"
tooltip="hi"
tooltip-trigger="click">
</button>
Then in your JavaScript, at any point, you can trigger the 'click' event to dismiss your tooltip. Make sure that the tooltip is actually open before you trigger the event.
// ... meanwhile, in JavaScript land, in your custom event handler...
if (angular.element('#myButton').scope().tt_isOpen) {
angular.element('#myButton').trigger('click');
}
Since this triggers the actual internals of AngularUI's Tooltip directive, you don't have the nasty side-effects of the previous solution.
Basically you cannot play with the tooltip-trigger to make this work. After digging through the ToolTip directive code, I found that the ToolTip attribute exposes a scope attribute called tt_isOpen.
So in your ng-click function, if you set this attribute to false, that will make the tooltip hide.
See the updated demo here
http://jsfiddle.net/3ywMd/10/
Like this
app.controller('MainCtrl', function ($scope) {
$scope.likes = 999;
$scope.doSomething = function(){
//hide the tooltip
$scope.tt_isOpen = false;
};
})
Michael's solution got me 90% of the way there but when I executed the code, angular responded with "$digest already in progress". I simply wrapped the trigger in a timeout. Probably not the best solution, but required minimal code
// ... meanwhile, in JavaScript land, in your custom event handler...
if (angular.element('#myButton').scope().tt_isOpen) {
$timeout( function(){
angular.element('#myButton').trigger('click');
}, 100);
}
For future reference, the accepted answer angular.element('.yourTooltip').scope().tt_isOpen will not work in new versions as tooltip has been made unobservable. Therefore, the entire tootlip is removed from DOM. Simple solution is to just check if tooltip is present in DOM or not.
Borrowing from #liteflier's answer,
// ... meanwhile, in JavaScript land, in your custom event handler...
if (angular.element('.yourTooltip').length) { //if element is present in DOM
setTimeout( function(){
//Trigger click on tooltip container
angular.element('.yourTooltipParent').trigger('click');
}, 100);
}

Resources