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

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.

Related

$setDirty() from jQuery is not working

I have created a directive which wraps a jQuery element, this directive is binded to an object which contains some callback functions as following:
vm.treeEvents = {
check_node: function(node, selected){
vm.form.$setDirty();
...
},
uncheck_node: function(node, selected){
vm.form.$setDirty();
...
}
};
In the directive post link function I have this :
if (scope.tree.treeEvents.hasOwnProperty(evt)) {
scope.tree.treeView.on(evt.indexOf('.') > 0 ? evt : evt + '.jstree', scope.tree.treeEvents[evt]);
}
so whenever an event declared in the treeEvents scope binding is triggered, the callback function is executed, and then the form is set to dirty state.
When I did this I noticed that the form is not passed to the dirty state unless I scroll the page or I click on some element in the form.
How can I solve this?
This is a common AngularJS issue, because of the fact that you jQuery trigger is "outside of Angular's world" you should let Angular to know about it via calling to $scope.$apply inside the event handler.
More info, read $scope.$apply docs.
I fixed this by using scope.$evalAsync();

Angularjs destroy is not working

http://plnkr.co/edit/UfQJU661pQR0DMY3c61t?p=preview
I got above code from AngularJs site and only thing I have added a button to delete a Div where we have controller but after delete no destroy method called as I have put alert in Directive and Controller.
element.on('$destroy', function() {
alert('destroy directive interval');
$interval.cancel(stopTime);
});
and
$scope.$on('$destroy', function() {
alert('destroy controller interval');
// Make sure that the interval is destroyed too
$scope.stopFight();
});
please suggest.
Thanks
The main thing to be noticed
When element.remove() is executed that element and all of its children will be removed from the DOM together will all event handlers attached via for example element.on.
It will not destroy the $scope associated with the element.
So you need to manually trigger scope.$destroy();
First get the scope of element:-
var scope = angular.element(document.getElementById("mainDiv")).scope();
Second remove the element from dom:-
$('#mainDiv').remove();
Third destroy scope manually:-
scope.$destroy();
Plunker
You're doing it outside of angular's context.
<button id="btn" onclick="DeleteMainDiv()">DeleteDiv</button>
So in your DeleteMainDiv() function
function DeleteMainDiv() {
alert('Controller div going to remove');
//debugger;
var scope = angular.element(document.getElementById("mainDiv")).scope();
$('#mainDiv').remove();
scope.$destroy();
}
This will trigger the destroy functionality.
But I don't see a need of it. Angular will automatically run the $destroy event handler when the route changes or directive no longer required.
DEMO

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.

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

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.

AngularJS collapse directive. Collapse all but self

I'm struggling to find the best solution for a collapse/expand directive that behaves like an accordion, ie. only one collapse/expand directive on the page must be open at any one time.
What is the best way to go about this, and get an expanding directive to tell the other directives to collapse? Can I use an isolated scope, a parent controller, broadcast events? Basically I'm having difficulties wrapping my head around inter-directive communication.
I know that there are accordion directives available, but I want to learn building directives myself. Thanks.
I ended up using $broadcast from a parent controller. The expanding directive asks the parent controller to broadcast a collapseChange event, which all directives listen for.
Parent controller
$scope.broadcastCollapseChange = function (id) {
$scope.$broadcast('collapseChange', { 'id': id});
};
Directive
scope.collapsed = true;
var onCollapseChange = function (v) {
if (scope.collapsed == false)
scope.$parent.broadcastCollapseChange(scope.$id);
}
scope.$watch('collapsed', onCollapseChange);
scope.$on('collapseChange', function (event, args) {
if (scope.collapsed == false && args.id != scope.$id)
scope.collapsed = true;
});
Currently I have to use $parent in the directive to get the parent controller, which is not very elegant. Is there any way I can get around this?

Resources