Angular directive load orders: Unknown Provider when used in Controller - angularjs

I am trying to use a directive, click-anywhere-but-here, in my header HTML, using controller navCtrl. Angular is throwing error:
Unknown provider: clickAnywhereButHereProvider <-
I'm thinking this has to do with how I'm using gulp to concatenate the JS files. I checked the concatenated main.js files with all JS, and see that navCtrl is defined above the clickAnywhereButHere directive. Not sure if this matters at all since the controller isn't using the directive at all, only the header.html file.
<header ng-controller="navCtrl">
<a click-anywhere-but-here="clickedSomewhereElse()" ng-click="clickedHere()">
<li>study</li>
</a>
</header>
How can I force the header to wait until clickAnywhereButHere directive is loaded before complaining?
Edit: Code:
navCtrl.js: I've gutted out a lot of the unrelated code
angular
.module('DDE')
.controller('navCtrl', ['$rootScope', '$location', '$scope', 'Modal', 'Auth', '$window', '$state', 'deviceDetector',
function($rootScope, $location, $scope, Modal, Auth, $window, $state, deviceDetector) {
$scope.clicked = '';
$scope.clickedHere = function(){
$scope.clicked = 'stop that';
console.log('clicked on element');
};
$scope.clickedSomewhereElse = function(){
console.log('clicked elsewhere');
$scope.clicked = 'thanks';
};
$scope.headings = [
{page: 'contact', route: '#/contact'}
];
}
]);
clickAnywhereButHere.js directive:
angular.module('DDE')
.directive('clickAnywhereButHere', function($document, clickAnywhereButHereService){
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
var handler = function(e) {
e.stopPropagation();
};
elem.on('click', handler);
scope.$on('$destroy', function(){
elem.off('click', handler);
});
clickAnywhereButHereService(scope, attr.clickAnywhereButHere);
}
};
});
clickAnywhereButHereService.js Service:
angular.module('DDE')
.factory('clickAnywhereButHereService', function($document){
var tracker = [];
return function($scope, expr) {
var i, t, len;
for(i = 0, len = tracker.length; i < len; i++) {
t = tracker[i];
if(t.expr === expr && t.scope === $scope) {
return t;
}
}
var handler = function() {
$scope.$apply(expr);
};
$document.on('click', handler);
// IMPORTANT! Tear down this event handler when the scope is destroyed.
$scope.$on('$destroy', function(){
$document.off('click', handler);
});
t = { scope: $scope, expr: expr };
tracker.push(t);
return t;
};
});
Both the directive and service are present in my min file:

You need to take into account the fact that your JS is minified.
So change this
.directive('clickAnywhereButHere', function($document, clickAnywhereButHereService){
to this
.directive('clickAnywhereButHere',
['$document', 'clickAnywhereButHereService',
function($document, clickAnywhereButHereService){
//...
}])

Related

Passing scope data from a function to parent scope?

In my controller I have a $http call which returns a json string which I then want to pass to a directive to be added to a map. The string is being passed from the controller to the directive fine but not from from the $http function within the controller to the directive.
wmm.controller('wapMapClr', ['$rootScope', '$scope', '$window', '$http', function ($rootScope, $scope, $window, $http) {
$scope.geobj = {};
$scope.geobj.geoprop = ""
// Search by postcode
// create a blank object to hold our form information
$scope.formData = {};
$scope.pcSearch = function () {
$scope.data = {};
$http.post('api/api.php', { postcode: $scope.formData } )
.success(function (result) {
$scope.geobj = {geoprop : result.json_string};
console.log($scope.geobj.geoprop);
Any help would really appreciated. Thanks
Promises are asynchronous, so you don't know when the promise returns, so it won't be immediately available for you
Your directive has a controller method, from where you can fire the $http call which you can access.
You can use $emit/$brodcast to listen to events passed from controller to your directive.
I am not sure what error you get, here is a fiddle with $timeout used which is async which works.
var myApp = angular.module('myApp',[]);
myApp.directive('passObject', function() {
return {
restrict: 'E',
scope: { obj: '=' },
template: '<div>Hello, {{obj.prop}}!</div>'
};
});
myApp.controller('MyCtrl', function ($scope, $timeout) {
$scope.obj = { prop: "world" };
$timeout(function(){
$scope.obj = { prop: "from timeout" };
},10000);
});
https://jsfiddle.net/jt6j82by/
Thanks Thalaivar. I modified the code you gave and it worked. See below:
wmm.controller('wapMapClr', ['$scope', '$window', '$http', function ($scope, $window, $http) {
$scope.geobj = {};
// Search by postcode
// create a blank object to hold our form information
$scope.formData = {};
$scope.pcSearch = function () {
$scope.data = {};
$http.post('api/api.php', { postcode: $scope.formData } )
.success(function (result) {
$scope.geobj = {geoprop : result.json_string};
Then in the directive...
wmm.directive('tchOlMap', function () {
var MAP_DOM_ELEMENT_ID = 'tchMap';
return {
restrict: 'E',
//BELOW IS THE LINE I CHANGED TO MAKE IT WORK!
scope: false,
replace: true,
template: '<div id="' + MAP_DOM_ELEMENT_ID + '" class="full-height"></div>',
link: function postLink(scope, element, attrs) {

Using AngularJS service to update scope in other controller from other module

I am a newbie for AngularJS so maybe I am looking at this the wrong way. If so, please point me in the right direction.
Basically I want to update some DOM elements that reside in another controller in another module.
I am trying to send data through a service but it seems that it is not updated on the destination scope.
var mainModule = angular.module('main', []);
var appModule = angular.module('app', ['main']);
appModule.controller("appCtrl", function ($scope, $routeParams, mainService) {
$scope.mainService = mainService;
var initialize = function () {
$scope.mainService.currentID = $routeParams.productId;
}
initialize();
});
mainModule.factory('mainService', function () {
var mainService = { currentID: 0 };
return mainService
});
mainModule.controller('mainCtrl', ['$scope', 'mainService', function ($scope, mainService) {
$scope.mainService = mainService;
$scope.function1Url = "function1/" + $scope.mainService.currentID;
$scope.function2Url = "function2/" + $scope.mainService.currentID;
//CurrentID is always 0!!
}]);
I expect that when calling the initialize() function in the appCtrl, it will see the currentID param in the service which is also used by the mainCtrl.
For updating controller using service, I strongly recommend you to use $rootScope.$broadcast and $rootScope.$on. Here is an example of how you can do it, and link to a blog:
$rootScope.$broadcast('myCustomEvent', {
someProp: 'Sending you an Object!' // send whatever you want
});
// listen for the event in the relevant $scope
$rootScope.$on('myCustomEvent', function (event, data) {
console.log(data); // 'Data to send'
});
http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Here is your working solution:
var mainModule = angular.module('main', []);
var productModule = angular.module('product', ['main']);
productModule.service('mainService', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
this.method1 = function () {
alert('broadcast');
$rootScope.$broadcast('myCustomEvent', {
newValue: 'This is an updated value!'
});
}
}]);
productModule.controller('mainCtrl', ['$scope', '$rootScope', function ($scope, $rootScope){
$scope.myValue = 'Initial value';
$rootScope.$on('myCustomEvent', function (event, data) {
$scope.myValue = data.newValue;
alert('received broadcast');
});
}]);
productModule.controller("productCtrl", function ($scope, mainService) {
$scope.mainService = mainService;
$scope.clickMe = 'Click to send broadcast';
$scope.callService = function () {
$scope.clickMe = 'Broadcast send!';
$scope.mainService.method1();
}
});
And HTML:
<body ng-app='main'>
<div ng-controller="mainCtrl"><b>My value:</b>{{myValue}}</div>
<div id="product" ng-controller="productCtrl">
<button ng-click="callService()">{{clickMe}}</button>
</div>
<script type="text/javascript">angular.bootstrap(document.getElementById("product"), ['product']);</script>
</body>
You have a couple different methods of doing this.
I agree with uksz, you should use broadcast/emit to let other scopes know of the change, let them handle as needed.
Broadcast goes to all child scopes of the element
$scope.$broadcast("Message Name", "payload, this can be an object");
Emit goes to all parent scopes of this element
$scope.$emit("message name", "payload, this can be an object");
Other option is you can also require the other controller
appModule.directive('myPane', function() {
return {
require: '^myTabs',
scope: {},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
}
};
});
Lastly you can include a function on the scope so you can let the parent scope know what's going on
appModule.directive('myPane', function() {
return {
scope: {
doSomething: '&something'
},
link: function(scope, element, attrs) {
$scope.doSomething(test);
}
};
});

Jasmine - unit testing a directive: Getting 'undefined' is not an object

I am taking baby steps in Jasmine, please bear with me for any blatant mistakes..I am writing test cases to check if a controller method - transformData gets called, the details below
My Directive
angular.module('myModule')
.directive('myDirective', [ function ()
{
'use strict';
return {
restrict: 'E',
templateUrl: '/static/quality/scripts/directives/hh-star-rating.html',
scope: {
varA:'#',
},
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
$scope.transformData = function(data)
{
/// does something;
return data;
};
}
],
My Spec
describe('directive - hh-star-ratings', function() {
'use strict';
angular.module('myModule', [])
.directive('myContainer', function() {
return {
restrict: 'E',
priority: 100000,
terminal: true,
template: '<div></div>',
controller: function($scope) {
$scope.loadingData = true;
this.stopLoading = function() {
$scope.loadingData = false;
};
}
}
});
var result_set = {
//some data-transform-req
};
beforeEach(module('myModule'));
var element, scope, controller;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element(
'<my-container> <my-directive> </my-directive> </my-container>'
);
$compile(element)(scope);
scope.$digest();
controller = element.controller('myDirective');
}));
it('should call the transformData', function() {
controller.transformData(result_set);
scope.$apply();
scope.transformData.should.have.been.called;
});
})
Issue: When I run the test, I get the following error
TypeError: 'undefined' is not an object (evaluating 'controller.transformData')
What am I doing wrong? Thanks in advance for your time.
Your define the function transformData in the scope - NOT in the controller
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
this.transformData = function(data) // <= change to this (controller)
{
/// does something;
return data;
};
}
],

Custom angular directive to load a route

Basically, what I need is an alternative to ngView that allows me to load a custom route inside an html element.
For example, this route:
$routeProvider.when('/some/path', {
templateUrl: 'some/template.html',
controller: someController,
controllerAs: 'ctrlAlias'
});
With this directive (with myRoute='/some/path'):
<div routeView="myRoute" />
Whould result in:
<div ng-controller="someController as ctrlAlias" ng-include src="some/template.html" />
Because of compatibility and legacy restrictions I can't use ui-router.
How do I implement this using a directive (or otherwise) ?
So, I adapted the code from ngView to do what i needed.
It is not very elegant since there is a lot of code duplication from angular-router module but it works perfectly.
Note that this only works with routes with templateUrl's and with routes without parameters.
Usage:
<div route-view="'/my/route'"></div>
<div route-view="myRoute"></div>
Code:
angular.module('app', [])
.directive('routeView', routeViewDirective)
.directive('routeView', routeViewFillContentDirective)
function routeViewDirective($animate, $parse, $q, $route, $sce, $templateRequest) {
return {
restrict: 'EA',
terminal: true,
priority: 400,
transclude: 'element',
link: routeViewDirectiveLink
};
function routeViewDirectiveLink(scope, $element, attributes, ctrl, $transclude) {
var model = $parse(attributes.routeView);
var currentScope;
var currentElement;
scope.$watch(model, update);
function cleanupLastView() {
if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
if (currentElement) {
$animate.leave(currentElement);
currentElement = null;
}
}
function update(path) {
var route = $route.routes[path];
if (route && route.templateUrl) {
var newScope = scope.$new();
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element);
cleanupLastView();
});
currentElement = clone;
currentScope = newScope;
} else {
cleanupLastView();
}
}
}
}
function routeViewFillContentDirective($compile, $controller, $parse, $q, $route, $sce, $templateRequest) {
return {
restrict: 'EA',
priority: -400,
link: routeViewFillContentDirectiveLink
};
function routeViewFillContentDirectiveLink(scope, $element, attributes) {
var path = $parse(attributes.routeView)(scope);
var route = $route.routes[path];
var templateUrl = route && $sce.getTrustedResourceUrl(route.templateUrl);
if (angular.isDefined(templateUrl)) {
$templateRequest(templateUrl).then(function(template) {
$element.html(template);
var link = $compile($element.contents());
if (route.controller) {
var controller = $controller(route.controller, { $scope: scope });
if (route.controllerAs) {
scope[route.controllerAs] = controller;
}
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
link(scope);
});
}
}
}

AngularJS: how to update a directive from another controller

Well, I'm having some problems updating a progress bar (which is in a directive) from a controller.
here are some code snippets:
my directive:
angular.module('TestApp').directive('orderProgress', ['$window', OrderProgress]);
function OrderProgress($window) {
var directive = {
link: link,
restrict: 'A',
templateUrl: 'OrderProgress.html',
controller: 'ProgressController',
replace: true
};
return directive;
function link(scope, element, attrs) {}
}
controller for directive:
function ProgressController($scope, progressNumberService) {
$scope.progress = progressNumberService.getProgress();
}
progressNumberService just hides the detail for the amount of "progress":
var progress = 20;
var progressServiceInstance = {
incProgress: function() {
progress += 20;
},
decProgress: function() {
progress -= 20;
},
getProgress: function() {
return progress;
}
};
App.value('progressNumberService', progressServiceInstance);
of course the controller:
function Controller($scope, progressNumberService) {
$scope.nextStep = function() {
progressNumberService.incProgress();
};
$scope.prevStep = function() {
progressNumberService.decProgress();
};
}
I've created an example:
http://plnkr.co/edit/LtY4ZUG591Kd3mUKEmEF?p=catalogue
So why doesn't the directive get the update from the 'Controller', when the Next/Prev buttons are pressed?
So your issue is that the value is being updated in your .value module, but your directive controller is never calling getProgress once the values are updated. I would suggest using $broadcast and $on to send a message saying that the progress was updated. I tested this and it seemed to do the trick.
Controller:
angular.module('TestApp').controller(controllerId2, ['$scope', '$rootScope', 'progressNumberService', ProgressController]);
function ProgressController($scope, $rootScope, progressNumberService) {
$scope.progress = progressNumberService.getProgress();
$rootScope.$on("event:progress-change", function() {
$scope.progress = progressNumberService.getProgress();
});
}
And change your .value to a factory so you can use $rootScope to broadcast
App.factory('progressNumberService', function($rootScope) {
return {
incProgress: function() {
progress += 20;
$rootScope.$broadcast("event:progress-change");
},
decProgress: function() {
progress -= 20;
$rootScope.$broadcast("event:progress-change");
},
getProgress: function() {
return progress;
}
}
});
Here is the updated Plunker DEMO

Resources