I'm trying to develop a directive that has it's own controller so it can collect the data it need's from the API and be injected anywhere.
Here's what I got so far:
(function () {
'use strict';
angular
.module('app.hostelsmg.bookings')
.directive('bookingsChart', bookingsChart);
function bookingsChart() {
return {
restrict: 'E',
scope: true,
controller: [function(){
$scope.test = 'Hi there!';
}],
compile: function(tElem, attrs){
tElem.html('{{test}}');
return function(scope, elems, attrs){
}
}
}
}
})();
So what I'd like, is the directive to get data from it's own controller. So far I couldn't get it to work that way.
Does anyone know how?
You can use something like this
function bookingsChart() {
return {
restrict: 'E',
scope: true,
controller: ['$scope', 'yourservice', function ($scope, yourservice) {
$scope.data = []; //bind this data to your view
$scope.getServiceData = function (count) {
//your service call
yourservice.getServiceData(count).then(function (data) {
$scope.data = data; //sets data in $scope.data variable
});
}
}],
link: function (scope, elements, attributes) {
//if you want to pass any parameters from your UI to your service method
scope.getServiceData(attributes.count); //calls the getServiceData in controller defined above
}
}
}
Related
app.controller('myController',['$scope',function($scope){
$scope.temp = '007'; // my controller variable
}]);
app.directive('mydir', function() {
return {
restrict: 'A',
transclude: true,
scope: { mydirobj: '=mydir' },
link: function(scope, element, attrs)
{
console.log(scope.temp);
// here i am trying to access 'myController' controller scope var
// getting error
}
};
});
app.controller('myController', ['$scope',
function($scope) {
$scope.temp = '007'; // my controller variable
}
]);
app.directive('mydir', function() {
return {
restrict: 'A',
transclude: true,
controller: 'myController', // add it here
scope: {
mydirobj: '=mydir'
},
link: function(scope, element, attrs) {
console.log(scope.temp);
// here i am trying to access 'myController' controller scope var
// getting error
}
};
});
You can see my comment inline.
Using directives I ended stuck when I needed to have more than one scope.
I'm building a data visualization app with Mongoose Node, Express and D3JS.
Here's the directive
angular.module('prodsChart', [])
.controller('businessCtrl', ['$scope','$http', 'BusinessSrv', 'Products', function($scope, $http, $location, BusinessSrv, Products) {
Products.get()
.success(function(data) {
$scope.products = data;
});
BusinessSrv.getTotal()
.success(function(data) {
$scope.businessSales = data;
});
}])
.directive( 'saleProd', [
function () {
return {
restrict: 'E',
link: function (scope, element) {
// Building the chart here
}
And the HTML :
<sale-prod></sale-prod>
Is it good to inject that way the Services in the Directive ?
Now I have 2 set of data in two $scope.
How do I use them in the directive ?
You can inject the $rootScope into your directive:
angular.module('prodsChart', [])
.directive( 'saleProd', ['$rootScope', function ($rootScope) {
// your code here
}]);
and then use it everywhere within the directive.
In 1st and 2nd example directive is in controller's scope and controller's datasets transferred to the directive as attributes. In 1st example directive can modify controller's data. In 2nd example directive use controllers data as strings and creates 2 objects 'productsObj' and 'salesObj' and can't modify parent's scope. It depends on how you handle attributes in the directive and how to transfer them into it. Just click on items in 'Product list (directive)' section and you'll see result.
1st: http://plnkr.co/edit/v46oEGHvUnxMNYsKAeaW?p=preview
var directive = function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'directive-template.html',
scope: {
products: '=',
sales: '='
}
};
};
html:
<div ng-controller="BusinessController as BusinessCtrl">
<sale-prod products="BusinessCtrl.products" sales="BusinessCtrl.sales"></sale-prod>
</div>
2nd: http://plnkr.co/edit/7CyIsqBNLbeZjyfbkGo9?p=preview
var directive = function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'directive-template.html',
scope: {
products: '#',
sales: '#'
},
link: function(scope, element, attrs) {
attrs.$observe('products', function(newVal) {
scope.productsObj = angular.fromJson(newVal);
});
attrs.$observe('sales', function(newVal) {
scope.salesObj = angular.fromJson(newVal);
});
}
};
};
html:
<div ng-controller="BusinessController as BusinessCtrl">
<sale-prod products="{{BusinessCtrl.products}}" sales="{{BusinessCtrl.sales}}"></sale-prod>
</div>
3rd example is just a piece of code that show's how to inject service in directive and controller. I add it because in you example i didn't see service injection in directive:
(function(undefined) {
var app = angular.module('prodsChart', []);
var controller = function($scope, Business, Products) {
// controller logic
};
var directive = function(Business) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// here you can use all Business service logic
}
};
};
var serviceBusiness = function() {
// business service logic
};
var serviceProducts = function() {
// products service logic
};
app.controller('BusinessController', ['$scope', 'Business', 'Products', controller])
.directive('saleProd', ['Business', directive])
.service('Business', serviceBusiness)
.service('Products', serviceProducts);
})();
html:
<div ng-controller="BusinessController as BusinessCtrl"></div>
<sale-prod></sale-prod>
You could inject a Service into a directive and use it to bring data across the application or use $emit.
A less elegant solution would be using a .value() and deal with it everywhere in your application.
How can I ensure that data from a controller has been loaded in a directive before the link function is run?
Using psuedo-code, I could have:
<my-map id="map-canvas" class="map-canvas"></my-map>
for my html.
In my directive I might have something like this:
app.directive('myMap', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: function ($scope, PathService) {
$scope.paths = [];
PathService.getPaths().then(function(data){
$scope.paths = data;
});
},
link: function(scope, element, attrs){
console.log($scope.paths.length);
}
}
}]);
The above won't work because console.log($scope.paths.length); will get called before the service has returned any data.
I know I can call the service from the link function but would like to know if there is a way to "wait" for the service call before firing the link function.
The easiest solution would be to use ng-if since the element and directive would be rendered only when the ng-if is resolved as true
<my-map id="map-canvas" class="map-canvas" ng-if="dataHasLoaded"></my-map>
app.controller('MyCtrl', function($scope, service){
$scope.dataHasLoaded = false;
service.loadData().then(
function (data) {
//doSomethingAmazing
$scope.dataHasLoaded = true
}
)
})
or use promises
return {
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: function ($scope, PathService) {
$scope.paths = [];
$scope.servicePromise = PathService.getPaths()
},
link: function (scope, element, attrs) {
scope.servicePromise.then(function (data) {
scope.paths = data;
console.log(scope.paths)
});
}
}
app.directive('MyDirective', function() {
return {
controller: function() {
this.$postLink = function() {
// here will run after the link function,
// and also after the binding came in
};
},
controllerAs: 'vm'
};
});
check out the angular 1.5 Components have a well-defined lifecycle and it works on directives to
Assume that I have a directive like this
<div my-directive callback='doSomething(myArg)'></div>
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'A',
scope: {
callback: '&'
},
link: function(scope, element, attrs) {
element.bind('someEvent', function() {
scope.callback({myArg: 'bla'});
});
}
}
});
If I want to pass a parameter to my scope's function, I have to do scope.callback({myArg: 'bla'}). I wonder if there's a way pass the argument without having to specify its name?
Use can use shared service in this case and inject it to directive:
angular.module("yourAppName", []).factory("mySharedService", function($rootScope){
var mySharedService = {};
mySharedService.values = {};
mySharedService.setValues = function(params){
mySharedService.values = params;
$rootScope.$broadcast('dataPassed');
}
return mySharedService;
});
And after inject it to directive. For example:
app.directive('myDirective', ['mySharedService', function(mySharedService){
return {
restrict: 'C',
link: function (scope, element, attrs) {
mySharedService.setValues(//some value//);
}
}
}]);
Then, you can get necessary value in controller.
function MyCtrl($scope, mySharedService) {
$scope.$on('dataPassed', function () {
$scope.newItems = mySharedService.values;
});
}
In documentation I can read next for the require option:
When a directive uses this option, $compile will throw an error
unless the specified controller is found. The ^ prefix means that this
directive searches for the controller on its parents (without the ^
prefix, the directive would look for the controller on just its own
element).
So I try to use it:
<div ng-sparkline></div>
app.directive('ngCity', function() {
return {
controller: function($scope) {}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs) {
// get weather details
}
}
});
But I have an error if my html have not ng-city attribute, so if I need controller of another directive - need to add exactly same attribute in html, but why (<div ng-sparkline ng-city="San Francisco"></div>)? And it looks on another directive's controller with this name (directive!!!) but not at controller with this name, is that true? Thanks. Just want to make it clear
With require you can get the controller of another (cooperating) directive. The controller in Angular is not semantically a function, but an object constructor, i.e. called essentially as var c = new Controller() (this is a simplification for the sake of clarity). Since the controller is an object, it can have properties and methods. By requiring the controller of another directive, you gain access to those properties/methods. Modifying your example to demonstrate:
app.directive('ngCity', function() {
return {
controller: function($scope) {
this.doSomething = function() {
...
};
}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs, ngCityController) {
// use the controller, e.g.
ngCityController.doSomething();
}
}
});
In your case, the city would be a property of the controller of the ngCity directive, exposed as a property. It will be read by the ngSparkline to know for which city the graph is about.
<b> added directives.js</b>
<code>
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngCity',
scope: {
ngCity: '#'
},
templateUrl: '/scripts/templates/tpl.html',
controller: ['$scope', '$http', function ($scope, $http) {
var url = "https://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=imperial&cnt=7&callback=JSON_CALLBACK&q=";
console.log(url + $scope.ngCity);
$scope.showTemp = function () {
$scope.getTemp($scope.ngCity);
};
$scope.getTemp = function (city) {
$http({
method: 'JSONP',
url: url + city
}).success(function(data) {
var weather = [];
angular.forEach(data.list, function(value){
weather.push(value);
});
$scope.weather = weather;
});
}
}],
link: function (scope, iElement, iAttrs, ctrl) {
scope.getTemp(iAttrs.ngCity);
scope.$watch('weather', function (newVal) {
if (newVal) {
var highs = [];
angular.forEach(scope.weather, function (value) {
highs.push(value.temp.max);
});
//chartGraph(iElement, highs, iAttrs);
}
});
}
}
}).directive('ngCity', function () {
return {
controller: function ($scope) {
//console.log("hello");
}
}
});
</code>
<b> and added tpl.htm</b>
<code>
<div class="sparkline">
<input type="text" data-ng-model="ngCity">
<button ng-click="showTemp()" class="btn1">Check {{ngCity}}</button>
<div style="color:#2743EF">{{weather}} ÂșC</div>
<div class="graph"></div>
</div>
</code>