Call directive API method from 'parent' controller in AngularJS - javascript

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

Related

AngularJS - $q undefined in a directive

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

How to properly watch scope for data load

I've built a custom Angular directive which uses D3.js to build a visualization. I reference this directive in my HTML like so:
<gm-link-analysis data="linkAnalysis.connections"></gm-link-analysis>
The relevant portion of directive code looks like this:
angular.module('gameApp')
.directive('gmLinkAnalysis', gmLinkAnalysis);
gmLinkAnalysis.$inject = ['$location', 'd3'];
function gmLinkAnalysis($location, d3) {
var directive = {
restrict: 'E',
templateUrl: '/app/gmDataVis/gmLinkAnalysis/gmLinkAnalysis.directive.html',
controller: 'LinkAnalysisController',
controllerAs: 'linkAnalysis',
scope: {
data: '='
},
link: function(scope) {
scope.$watch('data', function(json) {
console.log(json);
if (json) {
root = json;
root.fixed = true;
root.x = width / 2;
root.y = height / 2;
return scope.render(root);
}
});
...
}
};
return directive;
};
...and my controller below:
angular.module('gameApp')
.controller('LinkAnalysisController', LinkAnalysisController);
LinkAnalysisController.$inject = ['$routeParams', 'dataVisService'];
function LinkAnalysisController($routeParams, dataVisService) {
var vm = this;
var userId = $routeParams.userId;
var getConnections = function() {
dataVisService.getConnections({
userId: userId
}).$promise.then(function(connections) {
vm.connections = connections;
console.log(vm.connections);
});
};
var init = function() {
getConnections();
};
init();
}
It appears that my directive loads before my controller loads the data. I keep seeing undefined (from my log within the directive) followed by the data object I'm looking for (from my log within my controller). I understand that the directive would load before my asynchronous API call returns the data in my controller. What I do not understand is why the $watch does not pick up on this data when it finally is loaded. How would I go about getting this data into my directive?
The problem might be that your watch is not 'deep' enough. To watch whole object, not simply variable, you can pass third argument true to $watch function:
link: function(scope) {
scope.$watch('data', function(json) {
...
}, true);
...
If there is no need to go deep all the way, $watchCollection might be used.
More information here

Using Angular Directives with different approach

i'm using Angular directives like this:
'use strict';
var eventDirective = {
/**
* Initialize event directive and return the link
* calling all nested methods.
*
*/
init: function($scope, $element) {
var that = this;
return {
link: function(scope) {
scope.$watch('events', function() {
if (scope.events === undefined) {
return;
}
/**
* Every time the user access the event page, this methods
* will be called.
*
*/
__TableSorter__.init($element);
});
},
restrict: 'E'
};
},
__TableSorter__: {
init: function(element) {
console.log(element) // PRINTS ELEMENT
}
}
};
angular
.module('adminApp')
.directive('eventDirective', eventDirective.init.bind(eventDirective));
To illustrate I created this simple example. The TableSorter will run normally.
The problem is when I have several scripts, the code is too large. Is there any way to solve this? Maybe put scripts elsewhere as factories or services ?
My question is how to do this. I tried to inject a service within the directive but was resulting in undefined.
Thanks.
A good way to do should be, when you define your directive, you can set bindToController to true and right your logic inside a controller class. You may inject your services to that controller.
For example.
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
template: '<div></div>',
scope: {},
controllerAs: 'yourControllerClass',
bindToController: true
};
return directiveDefinitionObject;
});
yourControllerClass is angular controller here.

Trigger a function in a child directive from it's parent [angularJS]

So I totally do this in reverse all the time when using the directive property require: '^ParentCtrl' inside the child directive. Using require to then call the parent function; however, I need to do this in reverse.
Question:
How do I trigger FROM a parent directive the execution of a function IN a child directive.
Note:
1. Child Directive has no function is inside a link:
2. essentially I want a reverse require.
Parent Directive:
'use strict';
angular.module('carouselApp')
.directive('waCarousel', function() {
return {
templateUrl: 'views/carousel/wa.carousel.html',
controller: function($scope) {
var self = this;
// this function is being called based on how many pages there are
self.carouselElLoaded = function(result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
//when all the pages are loaded
if (Carousel.params.pageRenderedLength === Carousel.params.pageLength) {
Carousel.params.carouselReady = true;
// !!!!!!!! Trigger will go here!!!!!!!!!//
ChildCtrl.drawHotspots(); // (**for placement only**)
} else {
Carousel.params.carouselReady = false;
}
};
}
}
})
Child Directive:
'use strict';
angular.module('carouselApp')
.directive('waHotspots', function() {
return {
require: '^waCarousel',
link: function (scope, element, attrs, ctrl) {
//call this directive based on how
scope.drawHotspots = function () {...};
}
})
This is possible by having the parent controller talk to the child controller through a well defined API, that you create. The idea is that you want to maintain loose coupling between the parent and the child directive by having each respective controller knowing as little about each other as possible, but still have enough knowledge to get the job done.
To achieve this, require the parent directive from the child directive, and let the child directive register itself with parent's controller:
Child directive:
require: '^parentDirective',
controller: function(){
this.someFunc = function() {...}
},
link: function(scope,element,attr, parentCtrl){
parentCtrl.register(element);
}
Then in your parent directive, implement the register function, and get the child's controller, and call the child's function when needed:
Parent directive:
controller: function(){
var childCtrl = undefined;
this.register = function (element) {
childCtrl = element.controller();
}
this.callChildFunc = function (){
childCtrl.someFunc();
}
},
link: function (scope,element){
var ctrl = element.controller();
ctrl.callChildFunc();
}
You could always trigger it via a $watch. Just pass in the parent scope value that you want to watch and change it's value.
Parent:
$scope.drawHotspots = false;
Template:
waHotspots the-trigger="drawHotspots"....
Child Directive:
localTrigger: '#' // Receive the value to watch
scope.$watch('localTrigger',function() {
// call drawHotspots if value is set to true
});
Its on old topic but I came here today so might others ...
I think the best approche is to use a Service
angular.module('App').service('SomeService', [SomeService]);
Then inject the service into both the parent and child ...
controller : ['$rootScope', '$scope','SomeService', SomeDirectiveController],
Use the service to talk to each other ...
In their controllers SomeService.setParent(this) and SomeService.setChild(this)
Service would have a field to hold the references :
this.parentCtrl = null;
this.childCtrl = null;//or [] in-case you have multiple childs!
Somewhere in the parent : SomeService.childCtrl.someFunctionInChild()
Or if you want a restricted access , in service make the fields private :
var parentCtrl = null;
var childCtrl = null;//or [] in-case you have multiple childs of the same type!
this.callUserFunc = function(param){childCtrl.someFunctionInChild(param)};
And Somewhere in the parent : SomeService.callUserFunc(myparam)

Passing element attributes from parent to child with Angular JS 1.3

I have a problem with Angular JS. I have two directives.
angular.module('myModule', [])
.directive('myFirstDirective', function(){
return {
link: function (scope, elem, attr) {
var myAttributeToPass = attr.myFirstDirective;
scope.myAttr = myAttributeToPass;
},
controller: 'MyFirstController'
}
})
.controller('MyFirstController', function($scope){
this.returnTheParameter = function(){
return $scope.myAttr;
}
})
.directive('mySecondDirective', function(){
return {
require : ['ngModel', '^myFirstDirective'],
link : function($scope, element, attrs, ctrls) {
var ngModel = ctrls[0];
var myFirstCtrl = ctrls[1];
var theParamOfFirst = myFirstCtrl.returnTheParameter();
}
}
});
I init my first value with a string :
<div my-first-directive="foobar"> (... => my second directive is inside) </div>
My problem is in the life cycle, the returned value is always undefined because the controller is called before the link. When i do an isolated scope, with :
scope: {
"myProp": "#myFirstDirective"
}
That's works but i don't want to isolate the scope...
Any ideas ?
Thanks a lot !
The problem lies in the order in which the operations are taking place.
It sounds like you will need to compile things in a specific order. In which case I would like to refer you to this post: How to execute parent directive before child directive? so I don't borrow the full thunder of another person's explanation.
Ultimately you would want to do something along the lines of:
return {
compile: function(){
return{
pre:function (scope, elem, attr) {
var myAttributeToPass = attr.myFirstDirective;
scope.myAttr = myAttributeToPass;
},
post: angular.noop
};
},
controller: 'MyFirstController'
};
for your first directive and for the second directive:
return {
require : ['^myFirstDirective'],
compile: function(tElement, tAttrs, transclude){
return{
pre: angular.noop,
post: function($scope, element, attrs, ctrls) {
var ngModel = attrs.ngModel;
var theParamOfFirst = ctrls[0].returnTheParameter();
}
};
}
};
The angular.noop above is just an empty method returning nothing.
For a working example feel free to browse the plunk I threw together (http://plnkr.co/edit/pe07vQ1BtTc043gFZslD?p=preview).

Categories

Resources