I'm trying to inject $q into my directive, but though $q is defined as a resolver() at first, when calling the function it is undefined. Maybe something related to binding? I don't know.
(function () {
'use strict';
myForm.$inject = ["$q"];
angular
.module('myModule')
.directive('myForm', myForm);
function myForm($q) {
return {
restrict: 'EA',
scope: {
ngSubmitFunction: '&',
},
templateUrl: 'myTemplate',
controllerAs: 'ctrl',
controller: ["$scope", "$window", "$q", function ($scope, $window, $q) {
var vm = this;
vm.name = 'myForm';
$scope.submitPromise = function(){};
vm.ngSubmit = ngSubmit;
function ngSubmit($form) {
vm.submitDisabled = true;
$form.$setSubmitted();
if ($form.$valid) {
$scope.submitPromise().then(function() {
vm.submitDisabled = false;
});
}
}
}],
link: function (scope, element, attrs) {
console.log($q);
scope.submitPromise = function($q) {
console.log($q);
var deferred = $q.defer();
scope.ngSubmitFunction();
return deferred.promise;
}
}
};
}
}());
The objective is to call ngSubmit when user clicks on a button. ngSubmit disables the button, waits for the async calls to be over and then enables the button.
In the example code, the 1st console.log($q) (executed when loading the page) outputs this:
Q(resolver) {
if (!isFunction(resolver)) {
throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
}...
Which to me looks like correct.
But when calling submitPromise() after pressing the button, this is the output:
undefined
TypeError: Cannot read property 'defer' of undefined
When is $q lost?
Note: this is not the only version I tried, originally all code was on controller, nothing on link. I've also been told this pattern is deprecated and to use this one, which is better:
function submitPromise($q) {
return $q(function (resolve) {
$scope.ngSubmitFunction();
})
}
Nothing worked. Everything produces the same error, $q gets undefined at some point and can't find out why.
Using $q as an argument parameter is causing $q to become undefined.
myForm.$inject = ["$q"];
angular
.module('myModule')
.directive('myForm', myForm);
function myForm($q) {
return {
link: function (scope, element, attrs) {
console.log($q);
//scope.submitPromise = function($q) {
//Remove $q as parameter
scope.submitPromise = function() {
console.log($q);
var deferred = $q.defer();
//scope.ngSubmitFunction();
deffered.resolve(scope.ngSubmitFunction());
return deferred.promise;
}
}
The myForm function is a directive construction function to which the AngularJS framework will inject service providers. But the submitPromise function is not injectable; it is a child function of myForm. All injections should be done in the parent function.
Also the code can be simplified by using $q.when to create a promise.
link: function (scope, element, attrs) {
console.log($q);
//scope.submitPromise = function($q) {
//Remove $q as parameter
scope.submitPromise = function() {
console.log($q);
return $q.when(scope.ngSubmitFunction());
}
}
You should solve with this different injection
(function () {
'use strict';
angular
.module('myModule')
.directive('myForm', ['$q', function($q){
return {
. . .
}
}]);
Hope I've been helpful.
The right code is ( I removed parameter from function declaration ):
$scope.submitPromise=function() {
return $q(function (resolve) {//$q is available in function declared in the same scope
$scope.ngSubmitFunction();
});
}
Above code use $q variable from scope ( javascript scope not angular $scope ), $q is visible for all functions declared inside myForm function.
Your previous code used function parameter not $q from scope, parameter was not passed, so was undefined.
Javascript scope means everything between open tag { and close tag }. Check this example:
function(y){//scope start
var x; //scope local variable
var someFunc=function(){
//here is available y and x variables
};
//scope end
}
//outside of scope - here variables x and y not exists
Related
This is a relatively simple piece of code that calls a service and returns some data. I need to set the $scope with the result of the data. Is there an easy way to set this data to the scope without resorting to to binding the scope to the function in the then clause?
Angular Code
(function () {
var app = angular.module('reports', []);
var reportService = function($http, $q) {
var service = {};
service.getMenuData = function() {
var deffered = $q.defer();
$http.get('/Report/MenuData').success(function(data) {
deffered.resolve(data);
}).error(function(data) {
deferred.reject("Error getting data");
});
return deffered.promise;
}
return service;
};
reportService.$inject = ['$http', '$q'];
app.factory('reportService', reportService);
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = function(e) {
reportService.getMenuData().then(function(data) {
// Need to set the $scope in here
// However, the '$scope' is out of scope
});
}
};
reportMenuController.$inject = ['$scope', '$http', 'reportService'];
app.controller('ReportMenuController', reportMenuController);
})();
Markup
<div>
<div ng-controller="ReportMenuController">
<button ng-click="getMenuData()">Load Data</button>
</div>
</div>
There is absolutely no problem to set the $scope from within the function passed to then(). The variable is available from the enclosing scope and you can set your menu data to one of its fields.
By the way: You should consider to use then() instead of success() for your http request. The code looks much nicer because then() returns a promise:
service.getMenuData = function() {
return $http.get('/Report/MenuData').then(function(response) {
return response.data;
}, function(response) {
deferred.reject("Error getting data");
});
}
success() is deprecated by now.
I didn't notice the small detail missing in the plunker where my code was different.
(function () {
...
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = getMenuData;
function getMenuData(e) {
reportService.getMenuData().then(function(data) {
// Now I have access to $scope
});
}
};
...
})();
Notice the changes to the two lines as below:
$scope.getMenuData = getMenuData;
function getMenuData(e) {
This also begs a small question which is, "Why is it okay to set getMenuData to the $scope before it is declared?
I've got a directive that adds a click handler to an element:
module.directive('toggleSection', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function (event) {
scope.$apply(function () {
var scopeProp = 'show' + attrs.toggleSection;
event.preventDefault();
event.stopPropagation();
scope[scopeProp] = !scope[scopeProp];
return false;
});
});
}
};
}]);
When the element is clicked, it toggles another property on the scope, which another element is bound to with ng-show. It's working as it should in the app.
I've added the following test for the directive:
(function () {
'use strict';
// get the app module from Angular
beforeEach(module('app'));
describe('myCtrl', function () {
var $scope, $rootScope;
beforeEach(inject(function ($controller, _$rootScope_) {
$scope = {};
$controller('myCtrl', { $scope: $scope });
$rootScope = _$rootScope_;
}));
describe('the toggleSection directive', function () {
var testElement;
beforeEach(function () {
testElement = $compile('<a toggle-section="Test" href="#">Collapse section</a>')($rootScope);
$rootScope.$digest();
});
it('inverts the value of the specified scope property', function () {
$scope.showTest = false;
testElement.click();
expect($scope.showTest).toEqual(true);
});
});
});
In the real code there are properties like $scope.showSection1 = false and by adding console logs in the directive I can see the properties before and after clicking the bound element and they have the expected values (e.g. the property starts as false and after you click the toggle element once it changes to true).
However, the test always fails with 'Expected false to equal true'. I think it's to do with the $apply method, because none of the show properties seem to exist on the scope when I run the test.
Other tests I have (even in the same spec file), which don't use the directive can see properties on the scope just fine.
What am I doing wrong?
There are a few things to be changed in your test:
1 - scope creation should be changed from $scope = {} into $scope = $rootScope.$new();
2 - the directive should be compiled not into rootScope, but into scope
3 - the directive should first be created via angularjs.element and then compiled:
element = angular.element('<my-directive/>');
compile(element)(scope);
scope.$digest();
I've created some directives which have some functions, something like this:
myCtrl.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
'something': '='
},
link: function (scope, el, attr) {
function func1(){
//some stuff
scope.$apply();
}
function func2(){
//some stuff
scope.$apply();
}
function func3(){
//some stuff
scope.$apply();
}
//...
}
}
});
I have to call scope.$apply() in all the functions to update the view. In addition I don't want to define them in the controller. I don't know if there is a trick to avoid this kind of pattern. It is working but I think it's not a good solution.
UPDATE
There are more than 10 directives which we've created for some shapes, e.g. rectangle, circle, square etc. In these functions, which I called $apply in them, some methods like drag and scale are implemented. So I need to call $apply to modify changes in model and consequently the view will be updated. I don't know how can I make them able to aware scope automatically unless I write about 100 functions in a controller!
In addition, I'm using some 3rd party libraries like d3.js. Some event like clicked are bounded for calling these functions.
The real truth is that you need to call $scope.$apply() whenever you're changing something outside angular digest cycle. Angular detects the changes by dirty checking during the digest cycle.
$timeout is also doing $apply ($rootScope.$apply())
Check Angular source code.
What I can suggest is create helper function which will handle that. Example:
var ngAware = function (fnCallback) {
return function () {
var result = fnCallback.apply(this, arguments);
$scope.$apply();
return result;
};
};
// usage
function func1 () {
// impl...
}
var ngFunc1 = ngAware(func1);
// or
var func2 = ngAware(function func2 () {
// impl...
});
Sure you need apply()?
apply() is used when integrating 3rd party libraries (such as jQuery). In most cases, if you are not hooking up 3rd party javascript libraries, you can avoid $apply() by using the build in binding mechanism
For instance, the following snippet will call func1 using a 3 seconds timeout callback. then, func1 will manipulate the scope variable something and the view will be updated without the need to trigger $apply()
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.value = 'Hello';
});
app.directive('myDirective', function ($timeout) {
return {
restrict: 'E',
template:'<h1>{{something}}</h1>',
scope: {
'something': '='
},
link: function (scope, el, attr) {
function func1(){
scope.something += ' ' + scope.something;
}
$timeout(func1, 3000);
}
}
});
html
<body ng-controller="MainCtrl">
<my-directive something="value"></my-directive>
</body>
http://plnkr.co/edit/42VYs0W2BC5mjpaVWQg3?p=preview
perhaps if you explain more about your use case i could expand my answer to your specific needs
wraps the function within $scope.$apply()
check this :
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.value = 'Hello';
});
app.directive('myDirective', function ($timeout) {
return {
restrict: 'E',
template:'<h1>{{something}}</h1>',
scope: {
'something': '='
},
link: function (scope, el, attr) {
scope.$apply(function(){
function func1(){
//some stuff
}
function func2(){
//some stuff
}
function func3(){
//some stuff
}
})
}
}
});
If you have common functionality in you directives (circle, square, etc) you should group it. By example, we could have a directive to drag
.directive('ngDraggable', [function () {
return {
link: function ($scope) {
var stopHandler = function () {
$scope.apply(function () {
// Code to handler the end of the dag event
// Notify the other directive.
$scope.$emit('dragStopped', data);
});
};
// Initialization of the drag functionality.
}
}
}])
You can used as
<div ng-circle ng-draggable></div>
So, you earn three thing. First, the circle directive can do changes in its scope without call to $apply, because $emit already is in the digest cycle. Second, you only use $apply where is necessary. And third, you improve the performance by use $apply in scopes most small and with less children.
You could merge this answer with the answer of #sasko_dh
Maybe the Observer Pattern could be you useful.
I'm using (the awesome) Restangular and i'm running into something that forces me to use scope.$parent (not awesome), and i don't want to use that. It seems even though my controller is the parent scope to my directive's scope, the = isolated scope binding is evaluated before my parent controller is executed.
With the following HTML:
<div ng-controller="myController">
<div x-my-directive x-some-value="parentValue"></div>
</div>
And the following directive:
myApp.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, elem) {
console.log(scope.someValue); // Logs 'undefined' :(
},
scope: {
someValue: "="
}
}
});
And the following controller:
myApp.controller("myController", function($scope, allMyValues) {
allMyValues.getList().then(function(parentValue){
$scope.parentValue = parentValue;
});
}
As shown in my directives link function, evaluating a scope property that should have been bound to my parent's scope property returns undefined. However when i change my directives link function to the following:
myApp.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, elem) {
setTimeout(function() {
console.log(scope.someValue); // Logs '{1: number_1, 2: number_2}'
}, 2000);
},
scope: {
someValue: "="
}
}
});
How do i go about resolving this??
Thanks
that should helps:
myApp.controller("myController", function($scope, allMyValues) {
//add this line
$scope.parentValue={};
allMyValues.getList().then(function(parentValue){
$scope.parentValue = parentValue;
});
}
$scope.parentValue not exist until your request is resolved so add line like below to your code
sample demo http://jsbin.com/komikitado/1/edit
Looks like you are waiting for a promise to resolve before assigning the value to the scope.
There are a few ways you might handle this.
One way is to try moving the Restangular call to a resolve function for the view which holds the controller. Then you get access to the resolved data directly as an injection in your controllers
Another way might be to just assign the promise directly to the scope and then in the linking function wait for a resolution.
scope.someValue.then(function(value) { console.log(value); });
I have a situation where I want to create custom component, which should be reusable and provide public API to change it's state. I am trying to achieve this by building component using directive and controller.
What I desire to do is simply:
customComponent.apiMethod1( Math.floor( Math.random() * 2 ) );
Here is JSFiddle which should explain my case: http://jsfiddle.net/7d7ad/4/
On line 9 ( when user clicks a button ), I want to call line 22 method ( custom component public API method ). Is there anyway to achieve this?
You are looking for Providers. There are three different types: Factories, Services, and Providers. Each is a bit different you can take a look at this summary.
Providers can allow you to share common methods, functions and data between different areas of your application without duplicating code.
Short example - Fiddle
html
<div ng-app="myApp" ng-controller="testController">
<button ng-click="ClickMe()">Random</button>
{{display.value}}
</div>
javascript
angular.module('myApp', [])
.controller('testController', ['$scope','myService', function($scope, myService) {
$scope.display =new myService();
$scope.ClickMe = function() {
$scope.display.apiMethod1();
};
}])
.factory('myService', function() {
function factory() {
this.value = "Hello World";
this.apiMethod1 = function() {
this.value = Math.floor( Math.random() * 2 );
};
}
return factory;
});
You can, in addition to a service, use a parent directive with a controller.
Here is an example of how this might work (service example at the bottom):
app.directive('parentDir', function() {
return {
controller: function($scope, $element) {
var childFuns = [];
this.registerFun = function(func) {
childFuns.push(func);
}
//we will call this using ng-click
$scope.onClick = function(){
childFuns.forEach(function(func){
func.call(null,5)
});
}
}
}
})
And in the child directive:
app.directive('customcomp', function() {
return {
restrict: 'E',
scope: {},
require: '^parentDir', //we "require" the parent directive's controller,
//which makes angular send it as the fourth
//argument to the linking function.
template: '<h2>{{_currentNumber}}</h2>',
link: function(scope, elm, attrs, ctrl) {
scope._currentNumber = 0;
scope.apiMethod1 = function(val) {
scope._currentNumber = val;
};
//call the parent controller's registring function with the function
ctrl.registerFun(scope.apiMethod1);
}
}
});
Each child directive would "register" a function, and those functions can be stored and called from the parent directive in any way you want.
Note that you should use ng-click for events with angular.
FIDDLE
And here is how it might look with a service:
app.service('funcs', function(){
var funcs = [];
this.register = function(func){ funcs.push(func)};
this.call = function(){
funcs.forEach(function(func){
func.call(null,5);
})
}
})
FIDDLE