Angularjs directive require is not finding the parent directive controller - angularjs

When I require a controller in a directive, I am getting error saying that, not able to find the controller.
Please see the code with the issue below.
http://plnkr.co/edit/NzmQPA?p=preview
Can someone please have a look at it?
Thanks

You should use a service to communicate between them. Exactly how/what you do depends on your exact needs (there's not enough info in your post).
Side note, I changed your click handler to an ng-click.
Here's an example:
http://plnkr.co/edit/I2TvvV?p=preview
<div search-result-filter></div>
<div search-result-header ng-click="doClick()"></div>
angular.module('mymodule', [])
.controller('mainCtrl', ['$scope',
function($scope) {
$scope.test = "main angular is working";
}
]).controller('searchResultFilterController', ['$scope', 'myService',
function($scope, myService) {
//do something with 'myService'
}
])
.directive('searchResultFilter', [
function() {
return {
replace: true,
controller: 'searchResultFilterController',
template: '<h1>this is the first directive</h1>'
};
}
])
.directive('searchResultHeader', ['myService',
function(myService) {
return {
replace: true,
template: '<button>clickme</button>',
link: function($scope, $elem, $attrs) {
$scope.doClick = function() {
myService.someFn();
};
}
};
}
])
.service('myService', function() {
this.someFn = function() {
alert('this is working');
};
});

You should use require when your directives are related: like an accordion and accordion items.
To communicate between scopes, you should try $on, $emit, $broadcast. In your case, you need to inject rootScope into your directive, and broadcast an event from rootScope:
.directive('searchResultHeader',
function($rootScope) { //inject rootScope
return {
replace: true,
template: '<button>clickme</button>',
link: function($scope, $elem, $attrs) {
$elem.on('click', function() {
$rootScope.$broadcast("someEvent"); //broadcast an event to all child scopes.
});
}
};
}
);
Any scopes interested in the event can subscribe to it using $on:
function($scope) {
$scope.$on("someEvent", function() {
alert('this is working');
});
}
Using events is a way to create decoupled systems.
DEMO

Related

AngularJS Directive $scope is undefined

I have the following directive. When I trigger the open function and get to the debugger I get an error message in the console that says Uncaught ReferenceError: $scope is not defined(…).
How is it possible for $scope.open to be called when $scope is undefined?
app.directive('photo', ['$http', 'modal', function($http, modal) {
return {
replace: true,
templateUrl: '/assets/photo.html',
transclude: false,
scope: {
result: '=',
index: '#'
},
controller: ['$scope', '$http', 'modal', function($scope, $http, modal) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}]
}
}]);
Here is my DOM:
<div ng-repeat="r in results" photo result="r" index="$index"></div>
If I insert console.log($scope) just before my open function, and then again right before the debugger in that function, I get the following results. Left is before open is called, right is after open is called.
You inject the $http and modal in the directive definition (as you did), no need to in the controller function, just do:
controller: function($scope) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}
Try adding a statement that uses $scope in $scope.open. Chrome has probably optimized $scope away when you're in $scope.open because you're not using it.
$scope.open = function() {
console.log($scope);
debugger; //now you should see $scope.
};
its worked for me
var app = angular.module("moduleTest",[]);
app.directive("testDirective",function(){
return {
restrict: "A",
scope: true,
link: function(scope, element){
//code
//and $scope is scope
}
}
});
This should work:
app.directive('photo', ['$http', 'modal', function($http, modal) {
return {
replace: true,
templateUrl: '/assets/photo.html',
transclude: false,
scope: {
result: '=',
index: '#'
},
controller: function($scope, $http, modal) {
$scope.prev = $scope.index - 1;
$scope.open = function() {
debugger;
};
}
}
}]);
You need to define the $Scope at the top i.e.:
app.directive('photo', ['$http', '$Scope','modal', function($http, $Scope, modal)
It will work fine now.

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.

Reusable modal in Angular / Ionic

In AngularJS with Ionic, I would like to be able to call one modal from different controllers without having to duplicate the code related to the modal.
Here's how to create a modal (abbreviated from http://learn.ionicframework.com/formulas/making-modals/).
HTML:
<div class="card" ng-controller='MainCtrl' ng-click="openModal()">
Click here to open the modal
</div>
JS:
app.controller('MainCtrl', function($scope, $ionicModal)
{
$ionicModal.fromTemplateUrl('contact-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal
})
$scope.openModal = function() {
$scope.modal.show()
}
// functions for this modal
// ...
})
Now that's all fine an good, but if I want to open the same modal with the same functionality from a different controller, I would have to copy all the code related to it.
How can I abstract this to make my modals reusable and callable from different controllers?
Ideally, I would like each modal to have it's own "controller" (or similar concept), rather than having to put all of its code into the controller of whatever wants to open it.
This is a perfect scenario for a Directive.
Directive Code:
app.directive('myPopUp', ['$ionicModal', function($ionicModal) {
return {
restrict: 'E',
scope: {
externalScope : "="
}
replace: true,
templateUrl: 'path/to/your/template',
link: function($scope, $element, $attrs) {
$ionicModal.fromTemplateUrl('contact-modal.html', {
scope: $scope.externalScope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal
});
$scope.externalScope.openModal = function() {
$scope.modal.show()
};
}
};
}]);
And Your Controller(s):
app.controller('MainCtrl', ['$scope', function($scope) {
$scope.externalScope = {}
});
Whenever you want to include this in a partial just add:
<my-pop-up externalScope="externalScope"></my-pop-up>
The directive will have access to the controller and vice versa via the externalScope attribute. You can call $scope.externalScope.openModal() from your controller and it will trigger your directive modal to open.
Hope this was helpful.
The way i do it is a service
app.service('ModalService', function($ionicModal, $rootScope) {
var init = function(tpl, $scope) {
var promise;
$scope = $scope || $rootScope.$new();
promise = $ionicModal.fromTemplateUrl(tpl, {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
return modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModalService = function() {
$scope.modal.hide();
//$scope.modal.remove();
};
$scope.$on('$destroy', function() {
//$scope.modal.remove();
});
return promise;
}
return {
init: init
}
})
How to use it in a controller
app.controller('editMyProfileCtrl', function($scope,ModalService) {
$scope.openModal = function() {
ModalService
.init('my-modal.html', $scope)
.then(function(modal) {
modal.show();
});
};
$scope.closeModal = function() {
$scope.closeModalService();
};
})

How to broadcast events from directive to other controllers in AngularJS

I need somehow to emit event from part of the page (scrolling, clicking) that is served by one directive to other parts of the page, served by other controller so that it could be updated accordingly. Use case - for example Word document with annotations that are moving along with the page in the viewport.
SO in my design I have directive with link method in it and I need to broadcast events from it to other controllers in my app. What I have inside my link function:
element.bind('click', function (e) {
var eventObj = element.scrollTop();
scope.$broadcast('app.scrollOnDocument', eventObj);
});
This event cannot I cannot be see in other controllers directly - so code like this in other controller doesn't work:
$scope.$on('app.scrollOnDocument', function (e, params) {
console.log(e, params);
});
So what I have to do is to intercept those events in the same directive's controller and broadcast them to the higher scope like:
$scope.$on('app.scrollOnDocument', function(event, params){
//go further only if some_condition
if( some_condition ){
$rootScope.$broadcast('app.scrollOnDocumentOuter', params);
}
});
I am not sure this is the correct way of doing this. Maybe I am missing some directive property or setting to make it possible?
Non standard services can be passed to a directive like
.directive('notify', ['$rootScope', '$interval', function(rootScope, interval){
return {
restrict : 'E',
link : function(){
interval(function(){
rootScope.$broadcast('custom.event', new Date());
}, 1500);
}
};
}]);
The example below broadcasts an event every 1500ms.
If using the rootScope for communication cannot be avoided,you should always try unregistering the listener.
angular.module('app', [])
.controller('indexCtrl', ['$rootScope', '$scope',
function(rootScope, scope) {
scope.title = 'hello';
scope.captured = [];
var unregister = rootScope.$on('custom.event', function(evt, data) {
scope.captured.push(data);
});
scope.$on('$destroy', function() {
unregister();
});
}
])
.directive('notify', ['$rootScope', '$interval',
function(rootScope, interval) {
return {
restrict: 'E',
link: function() {
interval(function() {
rootScope.$broadcast('custom.event', new Date());
}, 1500);
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="indexCtrl">
<h1>{{title}}</h1>
<notify></notify>
<ul>
<li ng-repeat="event in captured">{{event|date:'medium'}}</li>
</ul>
</div>
For broadcasting in AngularJS, you always have to use $rootScope. You are listening always on $scope instead of $rootScope.

Extending a controller function within directive

I have a cancel function in my controller that I want to pass or bind to a directive. This function essentially clears the form. Like this:
app.controller('MyCtrl', ['$scope', function($scope){
var self = this;
self.cancel = function(){...
$scope.formName.$setPristine();
};
}]);
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
cancel : '&onCancel'
},
templateUrl: 'form.html'
};
});
form.html
<div>
<form name="formName">
</form>
</div>
However, the $setPristine() don't work as the controller don't have access on the form DOM. Is it possible to extend the functionality of controller's cancel within the directive so that I will add $setPristine()?
Some suggested using jQuery to select the form DOM, (if it's the only way) how to do that exactly? Is there a more Angular way of doing this?
Since the <form> is inside the directive, the controller should have nothing to do with it. Knowing it would break encapsulation, i.e. leak implementation details from the directive to the controller.
A possible solution would be to pass an empty "holder" object to the directive and let the directive fill it with callback functions. I.e.:
app.controller('MyCtrl', ['$scope', function($scope) {
var self = this;
$scope.callbacks = {};
self.cancel = function() {
if( angular.isFunction($scope.callbacks.cancel) ) {
$scope.callbacks.cancel();
}
};
});
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
callbacks: '='
},
templateUrl: 'form.html',
link: function(scope) {
scope.callbacks.cancel = function() {
scope.formName.$setPristine();
};
scope.$on('$destroy', function() {
delete scope.callbacks.cancel;
});
}
};
});
Use it as:
<custom-directive callbacks="callbacks"></custom-directive>
I'm not sure I am OK with this either though...

Resources