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

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)

Related

How do I reach $rootScope from a component?

I am very new to AngularJS/Ionic/Cordova programming and am trying to handle the visibility of a component using a global variable, so it can be hidden or shown from other components. I am creating the variable when calling the run function, assigning it to $rootScope.
app.run(function($rootScope, $ionicPlatform) {
$ionicPlatform.ready(function() {
// Some Ionic/Cordova stuff...
// My global variable.
$rootScope.visible = true;
});
})
My component is:
function MyComponentController($rootScope, $scope) {
var self = this;
self.visible = $rootScope.visible;
alert(self.visible);
}
angular.module('myapp')
.component('myComponent', {
templateUrl: 'my-component.template.html',
controller: MyComponentController
});
And the template:
<div ng-if="$ctrl.visible">
<!-- ... -->
</div>
However the alert message always shows "undefined". What am I missing?
$rootScope.visible isn't watched when being assigned as self.visible = $rootScope.visible. And it is undefined at the moment when component controller is instantiated.
It can be
function MyComponentController($rootScope, $scope) {
var self = this;
$scope.$watch(function () { return $rootScope.visible }, function (val) {
self.visible = val;
});
}
By the way, it is likely available as $scope.$parent.visible and can be bound in template as ng-if="$parent.visible", but this is antipattern that is strongly discouraged.
There may be better approaches:
top-level AppController and <my-component ng-if="visible">, so the component doesn't have to control its own visibility
broadcasting it with scope events, $rootScope.$broadcast('visibility:myComponent')
using a service as event bus (that's where RxJS may be helpful)
using a router to control the visibility of views, possibly with route/state resolver (this is the best way)
How about change self to $scope like this:
function MyComponentController($rootScope, $scope) {
$scope.visible = $rootScope.visible;
alert($scope.visible);
}

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

Angular Parent Scope Variable not changing

I have a parent controller where I set instantiate an object called links. I assign a property with a value that I want to change within another function. However when I set the variable in the instagramModel the links.imagesa doesn't get updated.
I print the value out in the console and the parentscope doesn't get updated. I have thought I followed the rules of prototypical inheritance.
Why is $scope.links.imagesa not updating?
.controller('HomeCtrl', function HomeController($scope, titleService, config, $sails, $timeout, $upload, leafletData, $modal, $log) {
$scope.links = {};
$scope.links.imagesa = "This should change";
$scope.instagramModal = function (size) {
var modalInstance = $modal.open({
templateUrl: 'instagramModal.html',
controller: 'InstagramModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $sails.get("/instagram/self").success(function (response) {
return response.data;
}).error(function (response) {
console.log('error');
});
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.links.imagesa = "wept";
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.ask = function () {
console.log($scope.links.imagesa);
};
});
If you want the parent's scope to get updated, then you must use $scope.$parent.links.imagesa since the changes made in child scope are not reflected in the parent scope directly.
I had the HomeCtrl instantiated in the UI Router and also on the template page using ng-controller. This messed up the scope.
Angular UI's modals use $rootScope by default. See documentation at "http://angular-ui.github.io/bootstrap/#/modal"
You can pass a scope parameter with a custom scope when you open the modal – e.g. scope: $scope if you want to pass the parent scope. The modal controller will create a sub-scope from that scope, so you will only be able to use it for your initial values.
Hence, if you want to update any value, keep the object/data in rootScope.

How to pass a http request result in my case?

I am trying to get the http request result to my child controller.
I have something like
<div ng-controller = "parentCtrl">
<button ng-click="callApi()">click me</button>
<div ng-controller = "childCtrl">
<div>{{productDetail}}</div>
</div>
</div>
angular.module('App').controller('parentCtrl', ['$scope','myFactory',
function($scope, myFactory) {
$scope.callApi = function() {
myFactory.request(id)
.then(function(data) {
$scope.productDetail = data
//do something in parent controller here....
})
}
}
]);
angular.module('App').controller('childCtrl', ['$scope',
function($scope) {
//I am not sure how to get the productDetail data here since it's a http request call.
}
]);
angular.module('App').factory('myFactory', function($http) {
var service = {};
service.request = function(id) {
return createProduct(id)
.then(function(obj) {
productID = obj.data.id;
return setProductDetail(productID)
})
.then(getDetail)
.then(function(productDetail) {
return productDetail.data
})
}
var createProduct = function(id) {
return $http.post('/api/product/create', id)
}
var setProductDetail = function(id) {
return $http.post('/api/product/setDetail', id)
}
var getDetail = function() {
return $http.get('/api/product/getDetail')
}
return service;
});
I was able to get the request result for my parentCtrl but I am not sure how to pass it to my child controller. Can anyone help me about it?
Thanks!
Potential approaches:
1) Inject myFactory into the child controller as well.
2) Access the parent scope directly from within childCtrl:
$scope.$parent.productDetail
3) If wanting to access from HTML
$parent.productDetail
Above assumes you are wanting to access that value specifically separate from a potential version on the child scope (existing code doesn't show that).
If it's a child scope, and nothing on the child scope (or a scope in between) is named productDetail, and you're not setting a primitive value in the child scope with that name, then you should be able to see the value directly through prototypical inheritance (but any of the three scenarios listed could force the need for a reference through the parent).

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

Categories

Resources