I am creating a custom directive in a controller and calling it in ng-repeat as follows:
HTML:
<div ng-controller="TestCtrl">
<div ng-repeat="page in pages">
<custom
load-data="loadData = fn">
</custom>
</div>
</div>
JS:
In Test directive I am calling loadData as follows:
scope: {
loadData: "&"
}
controller: ['$scope', '$element', '$timeout', '$filter', function ($scope, $element, $timeout, $filter) {
$scope.loadData({ fn: function(data) {
//Data calc.
}});
}
I am calling loadData from TestCtrl as follows:
App.controller('TestCtrl', function($scope, $http, $timeout, $rootScope) {
$scope.loadData(data);
}
I need to call loadData function but it is throwing error as undefined is not a function
Is there any way I can access scope of child directive from outside it. I went through few SO questions where it was mentioned that using ng-repeat changes scope, but I was not able to figure out a solution to it. I also tried using $broadcast and $on but that did not help
Could anyone please help me with it.
Thanks in Advance
I'm not sure to understand your request.. Your code doesn't make any sense, where is defined your data variable ? (controller: line 2), where is defined your fn function ? (html: line 4).
You got the error undefined is not a function which not surprising me because you never defined $scope.loadData method...
I tried to understand your question and produced this code snippet :
angular.module('demo', []);
angular.module('demo')
.directive('custom', function () {
return {
restrict: 'E',
template: '<div>{{ data || "loading..." }}</div>',
scope: {
loadData: '=',
page: '='
},
link: function (scope) {
scope.loadData(scope.page, function (data) {
scope.data = data;
});
}
};
});
angular.module('demo')
.controller('MainCtrl', function ($scope, $timeout) {
$scope.loadData = function (page, done) {
$timeout(function () {
done('data loaded data from ' + page.name);
}, 1000);
};
$scope.pages = [
{ name: 'page 1' },
{ name: 'page 2' },
{ name: 'page 3' }
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="demo" ng-controller="MainCtrl">
<div ng-repeat="page in pages">
<custom load-data="loadData" page="page"></custom>
</div>
</div>
Maybe this could help you.
Related
How can I access controller scope from multi-level directives in below structure:
I create a directive that has multi-level scopes inside its.
1. Controller scope
1.2. Directive1 scope(main directive)
1.2.1. Directive2 scope
1.2.1.1 Directive3 scope
I want to get the controller scope from directive 3.
please don't refer to $parent because the parent level it's not certain and a user may use this directive inside another directive.(see below codes)
<div ng-controller="Test">
<custom-dir>
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
<custom-dir>
</div>
The users create a function in the Test controller and the function will be called in my Directive 3 scope(how to get controller scope?).
<div ng-controller="Test">
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
</div>
More details(please don't refer to syntax error):
The controller is:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal, $http, $filter, $cookieStore, Common, $cookies) {
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
the dir1 directive:
App.directive("dir1", function ($compile, $filter, $rootScope, $timeout, Common, $window, $http) {
return {
restrict: "E",
priority: 1,
terminal: false,
templateUrl: "Content/html/Table.html?version=2.6",
scope: {
model: "=",
columns: "=",
config: "=",
search: "#",
},
link: function (scope, elem, attrs) {
scope.CallFunc = function (html) {
if (typeof (html) =="function" )
return html();
else {
return scope.$parent.$eval(html + "()", {});
}
}
}
}
});
the dynamic directive compile the runTest output
App.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
If I change the line $compile(ele.contents())(scope); to $compile(ele.contents())(scope.$parent.$parent); it's work.
In this directive, I need get the controller scope without $parent because
some users may use this directive inside another directive same below:
<custom-dir>
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
</custom-dir>
The using HTML tag
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
This issue handle with following codes:
A service for storing the controller scope:
App.service('TableService', function () {
return {
MyScope: null
};
});
Inject the TableService to dynamic directive(this directive compiles dynamic content):
App.directive('dynamic', function ($compile,TableService) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(TableService.MyScope);
});
}
};
});
And finally in the controller:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal,
$http, $filter, $cookieStore, Common, $cookies,TableService) {
TableService.myScope = $scope;
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
After that, the dynamic directive can access controller scope and all dynamic events(like testHtml) will be called even if the directive put in another directive(without using the $parent).
thank you #shaunhusain, huan feng for giving me an idea.
In child controller do something like:
$scope.$broadcast('yourEvent');
In parent controller do the listener:
$scope.$on('yourEvent' , function(){
//Handle your logic
});
A special case service
.service('DirectDispatcher', function(){
return {
fireMe: angular.noop
}
});
First directive registers a function callback
.directive(
...
link:function(DirectDispatcher){
function myHandler() {window.alert('just testing')}
DirectDispatcher.fireMe = myHandler;
}
...
);
Second directive fires the function callback
.directive(
...
link:function(DirectDispatcher){
DirectDispatcher.fireMe();
}
...
);
I would like to ask you if it is possible, and if yes, then how can I pass some variable from controller to directive.
Here is a bit of my code:
app.js
var VirtualGallery = angular.module('virtualGallery', ['VirtualGalleryControllers', 'ngRoute']);
VirtualGallery.constant('apiURL', 'roomPicture');
VirtualGallery.run(['$rootScope', function ($rootScope) {
$rootScope.roomPictures = [];
}]);
var VirtualGalleryControllers = angular.module('VirtualGalleryControllers', ['ngRoute']);
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$rootScope.roomPictures = data; //idk whether to use $scope or $rootScope
});
};
});
In this app.js I'm trying to get some data from DB, and that data I need to use in directive.
Directive
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
'getallrooms': '=',
'roomPictures': '='
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
//here I would like to be able to access $scope or $rootScope from app.js file.
};
}
};
});
In directive I need to gain access to $scope or $rootScope in function init() where I need to use that data.
HTML
<body ng-app="virtualGallery">
<div class="container" ng-controller="AppCtrl">
<div
id="webglContainer"
ng-webgl
getallrooms="getallrooms"
roomPictures="roomPictures"
></div>
<p ng-model="roomPictures"></p>
<p ng-model="getallrooms"></p>
</div>
<script type="text/javascript" src="js/vg.js"></script>
<script type="text/javascript" src="js/ngWebgl.js"></script>
In html I'm trying to pass that data from app.js to directive.
Im quite new to Angular and this is even my first directive, so I am bit confused. Every help will be appreciated. Thanks guys :)
In your app.js use the controller like this
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$scope.roomPictures = data; //use $scope instead of $rootScope
});
};
});
Then for your directive:
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
pictures: '=virtualGallery'
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
// you can access the variable through the scope
scope.pictures;
};
}
};
});
Or you could simply make the http request in your directive and manipulate the data there.
You can inject $rootScope to your directive ( like you did in your controller ) and then access that rootScope variable.
Let us say I have this html:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name="name">Hello, {{name}}!</my-directive>
</div>
with this simple controller:
myApp.controller('MyCtrl', function ($scope) {
$scope.name = 'Superhero';
});
And I have a directive in which I want to change the 'name' using require like this:
myApp.directive('myDirective', function($timeout) {
var controller = ['$scope', function ($scope) {
$scope.name = "Steve";
}];
return {
restrict: 'EA',
require: 'myName',
controller: controller,
link: function(scope, element, attrs, TheCtrl) {
TheCtrl.$render = function() {
$timeout(function() {
TheCtrl.$setViewValue('StackOverflow');
}, 2000);
};
}
};
});
But throws:
Error: No controller: myName
Here is the fiddle
But if I implement it using ng-model, works. Look here in this other fiddle
I have read that if you use 'require' in a directive, you need to have a controller for it.
So:
What I'm doing is wrong? It is not in this way? I need to do any other thing?
Well finally I got it.
Essencially what I'm trying to do is something called: 'Communication between directives using controllers'. I have found an article explaining this, and helped me a lot:
The view:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name>Hello, {{name}}!</my-directive>
</div>
As you see above, there are two directives: my-directive and my-name. I will call inside my-directive a function from the controller of my-name directive using require.
myDirective:
myApp.directive('myDirective', function($timeout) {
return {
require: 'myName',
link: function(scope, element, attrs, myNameCtrl) {
$timeout(function() {
myNameCtrl.setName("Steve");
}, 9000);
} // End of link
}; // return
});
myName:
myApp.directive('myName', function($timeout) {
var controller = ['$scope', function ($scope) {
// As I tried, this function can be only accessed from 'link' inside this directive
$scope.setName = function(name) {
$scope.name = name;
console.log("Inside $scope.setName defined in the directive myName");
};
// As I tried, this function can accessed from inside/outside of this directive
this.setName = function(name) {
$scope.name = name;
console.log("Inside this.setName defined in the directive myName");
};
}];
return {
controller: controller,
link: function(scope, element, attrs, localCtrl) {
$timeout(function() {
localCtrl.setName("Charles");
}, 3000);
$timeout(function() {
scope.setName("David");
}, 6000);
} // End of link function
};
});
Interesting and works like a charm. Here is the fiddle if you want to try it out.
Also, you can get communication between directives using events. Read this answer here on SO.
I have a directive as the following:
app.directive('fileInput', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('change', function () {
$parse(attrs.fileInput)
.assign(scope, element[0].files)
scope.$apply();
});
scope.$watch('files', function () {
console.log(scope.files);
});
},
controller: function ($scope, $element, $attrs) {
$element.bind('change', function () {
$parse($attrs.fileInput)
.assign($scope, $element[0].files)
$scope.$apply();
});
$scope.$watch('files', function () {
console.log($scope.files);
});
}
}
EDIT
and this is controller:
controllers.controller('RegisterCtrl', ['$scope', '$routeParams', '$location', '$http', 'Restangular', 'ServiceRepository',
function($scope, $routeParams, $location, $http, Restangular, ServiceRepository)
{
$scope.regService = function () {
$scope.error = {};
$scope.submitted = true;
var fd = new FormData();
fd.append("model", angular.toJson($scope.services));
console.log($scope.files);
}
}
And this is view file
<input type="file" id="boarding-picture_where_sleeping" class="form-control" file-input="files" multiple>
Additional info, regService() method is called when submitting the form
and when I debug $scope.files, it's available in console tab. but in my controller, it's undefined
so how to sync it between the directive and controller
Thanks
it's working now. the problem was caused I used 2 nested directives :)
Thanks guys
I would use scope and bindToController attributes in the directive definition, like in the following snippet from this blog:
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
bindToController: true,
template: '<div>{{ctrl.name}}</div>'
};
});
It requires use of the controllerAs syntax too, but you should be using that anyway as it is much more clear than passing $scope around everywhere and dealing with prototypical inheritance. This is recommended in John Papa's AngularJS Style Guide
I have a question, code like this:
HTML:
<div class="overflow-hidden ag-center" world-data info="target"></div>
js:
.directive('worldData', ['$interval', function($interval) {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>',
link: function($scope, element, attrs) {
$scope.target = {'aaa': 'aaa'};
aaa = $scope.chart;
}
}
}])
The chart value is undefined, and template no value, but when I declare $scope.target within controller, the code works, why?
This should be generally the pattern:
.controller('myController', function($scope){
$scope.target = {'aaa': 'aaa'}; //In reality, you'd normally load this up via some other method, like $http.
})
.directive('worldData', [function() {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>'
}
}])
--
<div ng-controller="myController">
<div class="overflow-hidden ag-center" world-data info="target"></div>
</div>
Alternatively, the directive could be responsible for going and fetching the data, and not pass in anything to it. You'd only want to consider that if you don't need the data in multiple places.