How to properly watch scope for data load - javascript

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

Related

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).

Call directive API method from 'parent' controller in AngularJS

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

Preloading data in a Directive using promises in a service using AngularJS

I hope someone can help me. I have not been able to figure this one out.
I wrote a directive (see below) to dump a pre-written ul-list on a page based on html data that I retrieved async from a database server. Both the Directive and The Service work.
I assumed that the "then" in "MenuService.getMenuData().then" would force a wait until the data arrived to the directive but some how the directive completes and shows the '3empty' message before the data arrived, which indicates that the directive completed earlier. I know I could put a timeout delay but that is not good. Do you have a suggestion as to what could the problem be?
The other technique I used was to put a ng-show="dataarrived" and set the dataarrived to true only when the promised completed. But same issue.
The purpose of this directive is to retrieve the Nav menu list from the serve and display it on the index.html but It does Not matter if I put this code in a controller or in a service or directive I get the same result. It shows nothing. It is particular to displaying it in the index.html before any other view is displayed.
Here is my directive if it make sense.
TBApp.directive('tbnavMenu', function ($compile, MenuService) {
var tbTemplate = '3empty';
MenuService.getMenuData().then(function (val) {
tbTemplate = val;
});
var getTemplate = function () {
return tbTemplate;
}
var linker = function (scope, element, attrs) {
element.html(tbTemplate).show();
$compile(element.contents())(scope);
}
return {
restrict: "E",
replace: true,
link: linker,
controller: function ($scope, $element) {
$scope.selectedNavMenu = GlobalService.appData.currentNavMenu;
$scope.menuClicked = function ($event, menuClicked) {
$event.preventDefault();
$scope.selectedNavMenu = menuClicked;
$scope.tbnavMenuHander({ navMenuChanged: menuClicked });
};
$scope.isSelected = function (menuClicked) {
return $scope.selectedNavMenu === menuClicked;
}
},
scope: {
tbnavMenuHander: '&'
}
}
}
I could be incredibly wrong but if your service is returning an $http object at the getMenuData method then these lines:
MenuService.getMenuData().then(function (val) {
tbTemplate = val;
});
should change to either:
MenuService.getMenuData().then(function (val) {
tbTemplate = val.data;
});
or
MenuService.getMenuData().success(function (val) {
tbTemplate = val;
});
My personal recomendation is to use the .then option as it enables the concatenation of more promises.

Categories

Resources