AngularJS - how to add javascript dependency in Directives? - angularjs

I have created a directives and while I call that directives, I need to load my JS first. Is there anyway to inject Javascript directly into Directives of angularjs?
Here is the code.
var directive = module.exports = function($rootScope, $location, service, user) {
return {
restrict: 'E',
link: function () {
$rootScope.$on( '$routeChangeSuccess', function(event,current) {
var path = $location.path();
var pageName = current.$$route.title ? current.$$route.title : path;
if(!current.redirectTo) {
user.then(function(userInfo) {
service.method(pageName, {
marketName: userInfo.marketName,
accountName: userInfo.name,
clientServices: userInfo.isClientServices(),
userType: userInfo.type
});
});
}
});
}
};
};
directive.$inject = ['$rootScope', '$location', 'service', 'user'];
Please do needful.

I dont think you can bundle a directive with js code (like web components), the js has to be included outside of the directive. You can however use angularjs dependency injection to inject the included angular services to your directive:
app.factory('myDependency', function() {
var myDependencyInstance;
return myDependencyInstance;
});
app.directive('directive', ['myDependency', function(myDependency) {
return {
restrict: 'E',
templateUrl: 'my-customer.html'
};
}])
But I guess its not what you want. You could bundle js into the directive's template, but then the js code would be duplicated every time you add the directive and this would propably only work with non angular js code.

Related

how realize click event on directive?

Please help fix the script.
i make simply directive:
angular.module('menus', [])
.directive("epMenu", ['$timeout', '$state',
function ($timeout, $state) {
return {
link: function (scope, element, attrs) {
scope.goTo = function(link) {
console.log('go to', link);
};
var navigationElem = angular.element("<div class='ep-menu-navigation'><li ng-click='scope.goTo('main')'>qwerty</li></div>");
angular.element('body').append(navigationElem);
},
restrict: "EACM"
}
}]);
but it does not work. I need to when you press the button, start function goTo()
Now the console following error message:
VM436 angular.js:12520 Error: [$injector:unpr] Unknown provider: $stateProvider <- $state <- epMenuDirective
live example
$state is a provider that is registered in the ui.router module, so you have to lay that dependency:
angular.module('menus', ["ui.router"])
Also, if you are building the template dynamically in the link function, you have to compile it so that angular can apply its actions to it:
.directive("epMenu", ['$timeout', '$state', '$compile',
function ($timeout, $state, $compile) {
return {
link: function (scope, element, attrs) {
scope.goTo = function(link) {
console.log('go to', link);
};
var navigationElem = angular.element("<div class='ep-menu-navigation'><li ng-click='goTo('main')'>qwerty</li></div>");
$compile(navigationElem)(scope, function(cloned){
angular.element('body').append(cloned);
});
},
restrict: "EACM"
}
}]);
You also had some other errors in your code:
Missing the angular ui router script
Using scope.goTo instead of just goTo
Not escaping the quotes in the goTo function
Using jqLite, you cannot use angular.element('body'), instead use the $document service
Here is a working example.
First of all you should have ui.router injected as dependency to your module.
angular.module('menus',["ui.router"])
If you want to change the state when clicked on a link created by a directive, create a controller for the directive.
use the $state.go in the controller to redirect.
In my answer, I used a controller, which changes the state to menu when clicked on the link of directive.
Here is the code to achieve it,
(function() {
angular.module('menus')
directive('epMenu', function () {
var controller = ['$scope','$state', function ($scope,$state) {
$scope.goTo = function(link) {
$state.go(link)
};
}],
template = "<div class='ep-menu-navigation'><li ng-click='goTo('main')'>qwerty</li></div>";
return {
restrict: 'EA', //Default in 1.3+
controller: controller,
template: template
};
});
}());
The error inside your code is because it does not find dependency for $state. you have to add angular.module('menus', ["ui.router"]) because $state is registered inside ui-router.also you have to add proper js for ui-router. You can bind click event inside link function.
angular.module('menus', ["ui.router"]).directive("epMenu", ['$timeout', '$state',
function($timeout, $state) {
return {
link: function(scope, element, attrs) {
element.find('button').bind('click', function() {
console.log("click");
});
},
restrict: "EACM"
}
}
]);
angular.module('menus',
["ui.router"])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
<div ng-app="menus">
<ep-menu>
this is normal text
<button>
Click Me
</button>
</ep-menu>
</div>

Angular: How to encapsulate logic in a directive?

I wonder how I can encapsulate functionality inside of an angular directive according to Rober C. Martin's "Clean Code" book. I want to omit comments and use functions with speaking names instead.
Imagine this code:
app.directive('myDirective' function() {
return {
link: function(scope) {
// initialize visual user state
scope.visualUserState = {
// some very detailed state initialization
}
}
})
To encapsulate the load functionality, I would like to replace this code like this:
app.directive('myDirective' function() {
return {
link: function(scope) {
scope.initializeVisualUserState = function() {
scope.visualUserState = {
// some very detailed state initialization
}
}
scope.initializeVisualUserState();
}
})
What I do not like on the second approach is that "loadDataFromServer" is some functionality that is only used by the link function and not by the view, so I violate the rule that scope should only hold data and functions that is used to interact with the view.
Also the code is not very readable I think.
As the functionality handles very private stuff of the directive, I think that using and injecting a service is not the right way to go.
What would be a better practice to encapsulate this functionality?
You should use a controller to add logic to your directive. In your controller you can inject Services. It's best to write a service for a single purpose, and simply let your controller call the services.
In fact you should only use the link function if you need to have your DOM-node, which is actually pretty close to never.
Read the styleguide by John Papa
angular.module('myModule', []);
// Controller
(function() {
angular
.controller('myModule')
.controller('MyController', ['$scope', 'DataService', function($scope, DataService) {
DataService
.retrieveData()
.then(function(data) {
$scope.visualUserState = data;
});
}]);
})();
// Directive
(function() {
angular
.module('myModule')
.directive('myDirective', function() {
return {
'restrict': 'E',
'scope': true,
'controller': 'MyController',
'controllerAs': '$ctrl'
};
});
})();
(function(module){
module.directive('myDirective', myDirective);
function myDirective(){
var directive = {
link : link,
scope : {state : '='},
restrict : 'EA',
template : //Some template which uses state
}
return directive;
function link(){
}
};
module.controller('myController', myController);
myController.$inject = ['$scope', 'OtherService'];
function myController(scope, service){
//service is used to pull the data and set to $scope.userState.
}
})(angular.module('myApp'))
And your directive will be :
<myDirective state="userState"></myDirective>
Let me know if this helps.

How to test angular directive scope: true

I am having some trouble reaching my return statement for my angular test. I am using jasmine with karma, and bard js. I used the ng2html preprocesser to $templateCache
templatesCached is the module name given to the files in my templates folder. ha.module.core Houses the directive that I want to test. I am reaching it when I run my debugging tools.
Below is my test. They pass, but the issue I am having is that rootScope does not hold any particular values. Also
element.html() // returns "" in the console. I was expecting my directive back. Is this wrong?
After I run through controller = element.scope I am getting
html = [div.ng-scope]
describe(" directive", function () {
var element,
template,
controller;
beforeEach(function () {
bard.appModule("templatesCached", "ha.module.core");
bard.inject(
"$compile",
"$controller",
"$rootScope",
"haConfig"
);
});
beforeEach(function () {
var html = angular.element("<div explore-hero></div>");
spyOn(myService, "getTemplateUrl");
//console.log("html ", html);
$rootScope = $rootScope.$new();
element = $compile(html)($rootScope);
$rootScope.$digest(element);
controller = element.scope();
element.controller('heroController');
Element.controller is an anonymous function.
console.log('element', element);
});
it("should have a div element", function () {
var result = element[0].querySelectorAll(".container");
expect(element.length).toBe(1); // not a ideal test for creation
expect(result).toBeDefined();
});
});
Here is my template cached.
module.run(['$templateCache', function($templateCache) {
$templateCache.put('/templates/explore-hero-base-template.html',
'<div class="container">\n' +
' <div share-widget></div>\n' +
'///html divs and info'
'</div><!-- .container -->');
}]);
My directive
angular.module('ha.module.core').directive('exploreHero', function(myService) {
var HeroController = function($scope) {
$scope.emit('methods')
};
return {
restrict: 'A',
scope: true,
templateUrl: myService.getTemplateUrl('explore-hero.html),
controller: 'HeroController
}
)
Any insight would help

AngularJS: require with bindToController, required controller is not available?

I have a directive where I user require and bindToController in the definition.
(function(){
'use strict';
angular.module('core')
.directive('formValidationManager', formValidationManager);
function formValidationManager() {
return {
require: {
formCtrl: 'form'
},
restrict: 'A',
controller: 'FormValidationManagerCtrl as fvmCtrl',
priority: -1,
bindToController: true
};
}
}());
According to the angular docs:
If the require property is an object and bindToController is truthy,
then the required controllers are bound to the controller using the
keys of the require property. This binding occurs after all the
controllers have been constructed but before $onInit is called. See
the $compileProvider helper for an example of how this can be used.
So I expect that in my controller:
(function () {
'use strict';
angular
.module('core')
.controller('FormValidationManagerCtrl', FormValidationManagerCtrl);
FormValidationManagerCtrl.$inject = ['$timeout', '$scope'];
function FormValidationManagerCtrl($timeout, $scope) {
var vm = this;
vm.API = {
validate: validate
};
//vm.$onInit = activate;
function activate() {
}
function validate() {
$scope.$broadcast('fvm.validating');
var firstInvalidField = true;
Object.keys(vm.formCtrl).forEach(function (key) {
var prop = vm.formCtrl[key];
if (prop && prop.hasOwnProperty('$setTouched') && prop.$invalid) {
prop.$setTouched();
if (firstInvalidField) {
$timeout(function(){
var el = $('[name="' + prop.$name + '"]')[0];
el.scrollIntoView();
}, 0);
firstInvalidField = false;
}
}
});
return firstInvalidField;
}
}
})();
vm.formCtrl will be populated with the form controller. However, it is undefined. Why is it undefined? Additionally, I tried to access it in the $onInit function but the $onInit function never got called. What am I doing wrong?
I am using the directive like so:
<form novalidate name="editUserForm" form-validation-manager>
I'm not sure if this is your issue, but I think your directive declaration should look like this:
controller: 'FormValidationManagerCtrl',
controllerAs: 'vm',
rather than:
controller: 'FormValidationManagerCtrl as fvmCtrl',
Looks like this is related to the angular version. I was in 1.4.6 which did not support this. I have upgraded to 1.5.0 in which the code in the OP is working great.

How to deal with more than one scope in an Angular directive?

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.

Resources