I have the following code,
HTML
<div ng-app="test">
<div ng-controller="containerCtrl">
<component data-module="components"></component>
</div>
</div>
JS
var test = angular.module('test', []);
test.controller('containerCtrl', ['$scope', '$rootScope', function ($scope, $rootScope) {
$scope.components = [];
$scope.$on('onSomething', function(e) {
$scope.components = $rootScope.config;
});
}]);
test.directive('component', function () {
var linkFn = function(scope, element, attrs) {
scope.$on('onSomething', function(e) {
console.log(scope, e, scope.module, e.currentScope.module);
/*
* Here 'module' is an array in both 'e' and 'scope' , but when I console it says [].
*/
console.log("onSomething!");
});
};
return {
restrict: 'E',
scope: {
module: '=module'
},
link : linkFn
};
});
test.run(['$rootScope', '$timeout', function ($rootScope, $timeout) {
$timeout(function(){
$rootScope.config = [{id: "c1", width: 100, height: 100 }];
$rootScope.$broadcast("onSomething", "");
},5000);
}]);
Fiddle http://jsfiddle.net/vFncP/4/
Problem
In run method, I have an ajax request which receives a configuration from the database. Once the request is complete, It is broadcast-ed to the scope level. Now the problem is, I am receiving the broadcast in a directive, when I console e or scope, I can see the module which has an array with data whereas when I console scope.module, it says []. I am not getting the error, am I doing something wrong?
Note: Adding a $scope.$watch might help, but I am trying to avoid $watch. Is there any other way to do it.
Thanks in advance
A solution could be $digest to refresh scope values:
scope.$digest();
http://jsfiddle.net/Anthonny/vFncP/7/
Related
I'm working on a fiddle that I found from this answer on SO
I've modified the fiddle for my situation in the following way:
angular.module('sampleApp', [])
.controller('myCtrl', function($scope) {
$scope.func = function() {
$scope.name = "Test Name";
}
})
.directive("myDirective", function($compile) {
return {
template: "<div>{{name}}</div>",
scope: {
name: '='
},
link: function(scope, element, attrs) {
alert(scope.name);
}
}
});
Basically, I am trying to pass a variable value from a scope function to the directive - but the alert message shows up an undefined value.
Any idea as to what is going wrong here? How can I pass the value stored within $scope.name within the $scope.func function and pass it to the directive?
The updated fiddle can be found here.
The problem is that you are defining name inside a function in your controller and that function is never called. Change to this.
angular.module('sampleApp', [])
.controller('myCtrl', function($scope) {
$scope.name = "Test Name";
})
.directive("myDirective", function($compile) {
return {
template: "<div>{{name}}</div>",
scope: {
name: '='
},
link: function(scope, element, attrs) {
alert(scope.name);
}
}
});
<div ng-app="sampleApp" ng-controller="myCtrl">
<div my-directive name="name">
</div>
</div>
The alert function is executing before the data is set by the controller.
To see value changes, add a controller and use $onChanges Life-Cycle Hook:
app.directive("myDirective", function() {
return {
template: "<div>{{name}}</div>",
scope: {
̶n̶a̶m̶e̶:̶ ̶'̶=̶'̶
name: '<'
},
controller: function() {
this.onChanges = function(changesObj) {
if (changesObj.name) {
alert(changesObj.name.currentValue);
};
};
}
}
});
Also note that one-time < binding is used instead of two-way = binding.
I've managed to get cross-domain HTML templates working by applying a url to the rootScope which I can access from controllers and other HTML files, but the problem arises when it comes to accessing the template from a directive. Here's my directive code:
angular.module("bertha")
.directive("bthToggleHeader", function() {
var controller = [
"$scope", "$rootScope", "_", function ($scope, $rootScope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
}
];
return {
restrict: "E",
scope: {
tglHeading: "#",
tglShowSection: "=",
tglOpen: "=?"
},
transclude: true,
controller: controller,
templateUrl: $rootScope.cdnUrl + "/html/directives/bthToggleHeader.html"
};
});
When attempting this I get: ReferenceError: $rootScope is not defined. Is there something blatantly obvious that I'm doing wrong here?
In a work project we tried using the link function but that didn't play nicely with being minified at all, hence the controller approach.
Any help would be greatly appreciated! Thanks.
You can use angular's dependency injection at directive level - so just place $rootScope there. See my example below:
angular
.module('bertha')
.directive('bthToggleHeader', ['$rootScope', function($rootScope) {
var controller = [
'$scope', '_',
function($scope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
}
];
return {
restrict: 'E',
scope: {
tglHeading: '#',
tglShowSection: '=',
tglOpen: '=?'
},
transclude: true,
controller: controller,
templateUrl: $rootScope.cdnUrl + '/html/directives/bthToggleHeader.html'
};
}]);
As Joe Clay said, $rootScope exists only in the controller function - that's why it's undefined outside of it.
$rootScope has fallen out of scope by the time you try to access it in templateUrl - you can't use a function parameter outside of the function (or at least, not without saving a reference somewhere)!
var controller = [
"$scope", "$rootScope", "_", function ($scope, $rootScope, _) {
if ($scope.tglOpen)
$scope.tglShowSection = true;
$scope.toggleShowSection = function() {
$scope.tglShowSection = !$scope.tglShowSection;
};
} // FUNCTION ENDS HERE - past this point $rootScope is undefined!
];
EDIT: While this answer gives some context on why your current code doesn't work, I wasn't 100% sure of the best way to solve the problem - Cosmin Ababei's answer seems like an effective solution, and I'd recommend you follow his advice!
I have an AngularJS SPA which loads articles into the view. Some articles have code examples and I want to use highlight.js to highlight it.
In my example below I have simulated a get request, 'cause that's how I load my dynamic content in the actual app. The $scope.test is very similar to what my actual app could get returned; some regular HTML to print out which includes code examples.
My problem: it doesn't really seem to work.
Specifically, nothing gets highlighted. It seems to me like I am missing an init or something... Halp?
I've also tried <div hljs/> with the same (lack of) result. There are no console errors.
This answer provides a solution that uses ng-model in the template. However, I don't use ng-model anywhere.
EDIT: I've made some changes to my example code to further explain the problem.
Here's my app (simplified):
var app = angular.module('app', ['ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.title = 'Some Title';
$scope.metaStuff = 'Written by Awesome MacFluffykins';
$scope.articleBody = '<p>Here\'s an example of a simple SQL SELECT:</p><pre><code class="sql" highlight>SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]);
Here's my HTML:
<div ng-app="app" ng-controller="ctrl">
<h2>{{ title }}</h2>
<p><small>{{ metaStuff }}</small></p>
<div ng-bind-html="articleBody"></div>
</div>
And finally a jsFiddle.
In my opinion it's best to use a directive for DOM manipulations like this. Pass your sourcecode through ng-model (you could also use another attribute) and run HLJS in the directive. Since you're using a asynchronous method to supply the value to your scope, you'll need to use $watch to catch the value and then run HLJS:
HTML:
<div highlight ng-model="test"></div>
Directive:
.directive('highlight', [
function () {
return {
replace: false,
scope: {
'ngModel': '='
},
link: function (scope, element, attributes) {
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal !== oldVal) {
element.html(scope.ngModel);
var items = element[0].querySelectorAll('code,pre');
angular.forEach(items, function (item) {
hljs.highlightBlock(item);
});
}
});
}
};
}
]);
Working JSFiddle: https://jsfiddle.net/1qy0j6qk/
Fiddle
https://jsfiddle.net/vg75ux6v/
var app = angular.module('app', ['hljs', 'ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.test = '<h2>Here\'s some code:</h2><pre><code hljs class="sql">SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]).directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}]);
Just wondering what's the best way to communication from controller to directive function, i have got an ng-click on one of the button, but the function sit in the directive, is there a way i can call the function within the controller (which sits in directive). i understand u can apply double binding with scope, is there any better way of doing so?
Cheers
app.controller('leadsListing', ['$scope', function($scope){
$scope.filterresultcount = 0;
$scope.records = [];
$scope.filtertotal = '';
$scope.$watch('filtertotal', function(){
$scope.filterresultcount = parseInt($scope.filtertotal / 20);
});
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.heyJoe(); // It's in diretive
};
}]);
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount'
},
restrict: 'A',
replace: true,
link: function($scope, iElm, iAttrs, controller) {
$scope.heyJoe()
}
}
}
I believe the best way to implement this kind of controller --> directive communication is to use $scope.$broadcast from the controller, and $scope.$on in the directive's controller/ linking function.
Controller:
app.controller('leadsListing', ['$scope', function($scope){
// ...
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.$broadcast('joeCalled');
};
}]);
Directive:
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount'
},
restrict: 'A',
replace: true,
link: function(scope, iElm, iAttrs, controller) {
scope.$on('joeCalled', function(){
// Do something...
});
});
};
}
Edit:
Created a working example of this technique:
http://jsfiddle.net/9p3eyy5h/2/
Calling a function directly in the directive from the controller could be done by placing an empty object on the controller scope, binding it to the directive's scope with '=', and attaching a function to it in the directive's linking function/ controller, which could later be called by the wrapping controller.
Controller:
app.controller('leadsListing', ['$scope', function($scope){
// ...
$scope.directiveFuncs = {};
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.directiveFuncs.heyJoe();
};
}]);
Directive:
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount',
// Binding to the controller's func obj
funcs: '='
},
restrict: 'A',
replace: true,
link: function(scope, iElm, iAttrs, controller) {
scope.funcs.heyJoe = function(){
// Do something...
}
});
};
}
HTML:
<div ng-controller="leadsListing">
<div recordfilter funcs="directiveFuncs"></div>
</div>
I would however advise to use my other approach, as it prevents direct dependency between the controller and the directive, and therefor, more robust, so it won't throw an error if the directive is missing or changes.
Working example:
http://jsfiddle.net/9pm3zg5s/1
I have a controller (code below) that links to a d3-cloud directive and works perfectly. Data is added in the controller and passed to the directive.
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore){
$scope.d3Data = [
{
'kword': 'a',
'count': 141658,
},{
'kword': 'b',
'count': 105465,
}
];
}]);
Now I'm trying to pull data from a JSON request service by switching my controller to the following code. When I do a console.log in the controller underneath the $scope.d3Data = data line, everything appears to be working properly (the proper data is returned).
However, something breaks when trying to link the controller to the directive, for some reason the directive is getting an undefined/null data set.
I'm wondering if the issue is in the order with which the code executes. Perhaps the controller tries to pass data to the directive before the JSON service has finished, thus resulting in no graph being drawn. Could this be happening, and if so, how can I go about fixing it?
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore){
$rootScope.$on('updateDashboard', function(event, month, year) {
updateDashboard(month, year);
});
var updateDashboard = function(month, year) {
requestService.getP2PKeywordData(month, year).then(function(data) {
$scope.d3Data = data;
});
};
updateDashboard($cookieStore.get('month'), $cookieStore.get('year'));
}]);
EDIT: Directive code:
myApp.directive('d3Cloud', ['$window',
'd3Service',
'd3Cloud',
function($window,
d3Service,
d3Cloud) {
return {
// Restrict usage to element/attribute
restrict: 'EA',
// Manage scope properties
scope: {
// Bi-directional data binding
data: '=',
// Bind to DOM attribute
label: '#'
},
// Link to DOM
link: function(scope, element, attrs) {
// Load d3 service
d3Service.d3().then(function(d3) {
// Re-render on window resize
window.onresize = function() {
scope.$apply();
};
// Call render function on window resize
scope.$watch(function() {
return angular.element($window)[0].innerWidth;
}, function() {
scope.render(scope.data);
});
// Render d3 chart
scope.render = function(data) {
// d3 specific appends... not important
HTML Code: (simple enough)
<div class="col-md-6">
<div class="module">
<div class="inner-module" ng-controller="DownloadsCloudCtrl">
<div class="module-graph">
<d3-cloud data="d3Data"></d3-cloud>
</div>
</div>
</div>
</div>
try adding a $scope.$apply() after $scope.d3Data = data;
$scope.d3Data = data;
$scope.$apply();
if this doesn't work, you can always pass a function down into the directive and set it to update the data and then manually call it from the controller:
so controller logic:
$scope.updateDirective = function () {}; // this will be overridden in directive
directive logic:
scope: {
data: '=',
update: '&updateDirective'
label: '#'
}
scope.updateDirective = function () {
scope.render(scope.data); // call to update function
};
markup:
<div class="col-md-6">
<div class="module">
<div class="inner-module" ng-controller="DownloadsCloudCtrl">
<div class="module-graph">
<d3-cloud data="d3Data" update-directive="updateDirective"></d3-cloud>
</div>
</div>
</div>
</div>