How do directives control each other? - angularjs

I have two directives:
module.directive('modal', function () {
return {
restrict: 'A',
templateUrl: "/templates/ui/components/modal.htm",
replace: true,
link: function ($scope, element, attrs) {
console.log("Loaded modal directive.");
},
controller: function ($rootScope, $scope) {
$scope.isOpen = false;
$scope.open = function () {
$scope.isOpen = true;
};
$scope.close = function () {
$scope.isOpen = false;
};
// Global
$rootScope.$on('openModal', function () {
console.log("open"); // How to call open function here?
});
$rootScope.$on('closeModal', function () {
console.log("close"); // How to call close function here?
});
}
}
});
module.directive('popUpWindow', function () {
return {
restrict: 'A',
templateUrl: "/templates/ui/components/pop-up-window.htm",
replace: true,
transclude: true,
link: function ($scope, element, attrs) {
console.log("Loaded pop-up window directive.");
$scope.title = attrs["title"];
},
controller: function ($rootScope, $scope) {
$scope.isOpen = false;
$scope.open = function () {
$scope.isOpen = true;
$rootScope.$emit('openModal');
};
$scope.close = function () {
$scope.isOpen = false;
$rootScope.$emit('closeModal');
};
}
}
});
The idea is that there is only ever one modal for all possible instances of popUpWindow.
I have no idea how to get these two to talk to each other. How do I get hold of my modal and call it's open and close functions? Where should I put the open and close functions, controller or link? It seems like the controller but the link has access to element.. Should I be looking to get an instance of the modal or is there some neat Angular way of doing this?
I've got a function on the modal firing using $emit on the pop up window but this doesn't feel like a great way of communicating between directives.
I've trawled the net and tried a lot of stuff but not found anything that seems to fix this. It doesn't seem obvious.

Directives are a way to teach HTML new tricks and it is primarily used for creating some reusable HTML components, hence the functionality of a directive should be self-contained and it should be independent on other directives.
If you want to maintain multiple popup windows, for instance, you should make the popupWindow directive to be driven by the model passed in. And in your controller, you should be able to track all popupWindows by tracking the model.
Directives should talk to each other via controller though you can make it happen by using message broadcasting.
Hope it can shed some light on.

Related

Call method in controller from directive

HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}

AngularJS inject $timeout to a link?

So here is what my issue is:
i have a directive:
autocompleteDirective.$inject = ['$timeout'];
function autocompleteDirective($timeout) {
return {
restrict: 'E',
scope: {
searchParam: '=ngModel',
suggestions: '=data',
onType: '=onType',
onSelect: '=onSelect',
autocompleteRequired: '='
},
controller: autocompleteController,
link: autocompleteLink,
templateUrl:'modules/components/autocomplete/templates/autocomplete.html'
};
}
my Link function looks like this:
function autocompleteLink(scope, element, attrs) {
$timeout(function() {
scope.initLock = false;
scope.$apply();
}, 250);
.... some other code
}
and my controller (not really relevant) :
autocompleteController.$inject = ['$scope'];
function autocompleteController($scope) {
//scope code
}
in my link function, I have a function that is (at the moment) using setTimeout:
if (attrs.clickActivation) {
element[0].onclick = function() {
if (!scope.searchParam) {
setTimeout(function() {
scope.completing = true;
scope.$apply();
}, 200);
}
};
}
I would like to unit test this certain block of code, but my unit tests fails:
elem.triggerHandler('click');
expect(scope.completing).to.equal(true);
even though, in the coverage report, i can see that the logic does successfully execute when the triggerHandler is clicked.
what i believe the culprit is the timeout.
digging around SO and other websites, i found using $timeout works best due to its exposure to "flush()" method.
my question is, how do I "inject" $timeout to the link function?
the previous examples i have see like injecting $timeout directly to directive, and then nesting the link function() inside the directive declaration:
function directive($timeout){
return {
link: function(scope, attrs) {
$timeout(blah!) //timeout is useable here...
}
}
The above doesn't work for me since i am not creating the function inside the directive function...so based on the model i am using, how can i use $timeout in a link?

How to open `modal window` from the header controller to main controller using service?

I have couple of links in the header. But the pop-up nested under the other controller. I am using a service call for the pop-up here.
clicking on the header link, how can i open the pop-up which nested in the other controller.
And i need to update the content of the pop-up according to the link what the user clicks. for that, i included the html (using ng-include)
here is my code and demo :
var app = angular.module('myApp', []);
app.service('modalService', function() {
this.width = 100;
this.height = 100;
});
app.directive('modalDialog', function(modalService) {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true,
link: function(scope, element, attrs) {
},
templateUrl: "modal.html"
};
});
app.controller('MyCtrl', ['$scope', function($scope, modalService) {
$scope.modalShown = false;
}]);
app.controller('header', ['$scope', 'modalService', function($scope, modalService) {
$scope.modalShown = false;
$scope.toggleModal = function(link) {
console.log(link);
$scope.linkInfo = link+'.html';
$scope.modalShown = !$scope.modalShown;
};
}]);
Live demo
In case the way what i do is wrong, please correct me. at present the fuction is calls and gettting console. but pop-up not opening with appropriate content.
You can use the modal service of angular-UI library angular-ui#modal
If you want to just create something simple, you have to change your code in following ways:
I would recommend using ngIf for showing/hiding your modalDialog directive. With it, it will be rendered when the expression becomes truthy and you can get the right template each time.
Do not transclude since you don't have static templates. You have to compile the template on runtime (using $compile service). Also create a new scope for that template.
app.directive('modalDialog', function(modalService, $rootScope, $compile, $templateCache) {
return {
restrict: 'E',
scope: {
show: '=',
templateUrl: '='
},
link: function(scope, element, attrs) {
var template = $templateCache.get(scope.templateUrl);
var modalScope = $rootScope.$new();
var modalElement = angular.element(template);
element.append(modalElement);
$compile(modalElement)(modalScope);
},
templateUrl: "modal.html"
};
});
Note that there are still issues. Your placing of modal into arbitrary location into a DOM may not work (not all browsers behave correctly when playing with positioning). So I would recommend creating a service that would append and render the modal just below the element.

Controller executes twice, what is the best solution?

I know where the problem is i just don't know how to go about fixing it. I have two directives that call the same controller and after research i found out its a bad thing and i should use a service or something.
Now i believe i have to communicate between both these controllers. Every time i do a console.log inside the controller it runs twice.
What should i do?
Directives
app.directive("sidemenu", function() {
return {
restrict: 'A',
templateUrl: 'partials/sidemenu.html',
scope: true,
transclude : false,
controller: 'taskbarController'
}
});
app.directive("taskbar", function() {
return {
restrict: 'A',
templateUrl: 'partials/taskbar.html',
scope: true,
transclude : false,
controller: 'taskbarController'
}
});
Controller:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller',
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller) {
$scope.inbox = Poller.msgdata;
$scope.project = Poller.newdata;
$scope.projects = Poller.projects;
$scope.messages = Poller.messages;
console.log($scope.inbox);
$scope.sendMessage = sendMessageModal.activate;
$scope.showModal = function() {
projectsModal.deactivate();
projectsModal.activate();
};
$scope.logout = function () {
authData.get('logout').then(function (results) {
authData.toast(results);
$location.path('login');
});
}
authData.get('session');
$scope.toggle = function(){
$scope.checked = !$scope.checked
projectsModal.deactivate();
sendMessageModal.deactivate();
}
}]);
You could still use controller (rather then service) as long as you are using it to bind the view (for e.g.) If you want to make a webservice call (for e.g.) then I would use service.
Thing you need to think about is that do you need two directive to share same service or just scope? If they (directive) are functionally different then use separate service/contr (Single Responsibility Principal) and if they have some shared data/scope then think about how to cater for that.If you want to share scope between controllers then you can use service which gets injects into the controller.

How can I change information in another view via an AngularJS Directive?

Okay, this is a convoluted question and I may be asking for a bad approach. If that's the case, please do let me know.
So I have a directive for a navigation bar. When someone clicks something, I managed to get the directive to add a class and therefore load the bar. Thanks to StackOverflow.
But now, I have a service that gets and sets values. When a value is changed in the service, I want to reflect that in a view. Is such a thing possible?
EDIT
For clarification, if I do use a $apply(function()...., how exactly do I do that? My view has something like. My view is not bound to any particular controller, or scope. Not sure if it should be. But here's a snippet of my view:
<p>
Are you sure you change that song,
<br />
{{ songs[0].title }}
</p>
Here's my directive:
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.ms = ModalService;
$scope.songs = {};
$scope.$watch('ms.songs', function(newVal, oldVal) {
if(newVal != null) {
$scope.$apply(function() {
$scope.songs = newVal;
});
}
});
},
Have you tried this?
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.songs = ModalService.songs;
}
});
I did run into a scenario recently where I had to setup a watch on a service property within a directive and the solution was to setup the watch within the link function similar to:
link: function(scope, element, attrs) {
// other link code
scope.$watch(function() { return svc.property; }, function(data) {
// do something here
});
// other link code
}

Resources