Directive link() querySelector - angularjs

This it would be my directive
I try to add click events in the link function for my directive when it initiates, but it gives me empty element when I try to get the elements.
var link = function(scope, elem, attrs, controller){
console.log('WidgetId:' + controller.widget);
//Not working,
angular.element(elem[0].querySelectorAll('.widget-btn')).on('click', function(){
console.log('click event with querySelector');
});
// Directive element DOM
console.log(elem);
// That call return empty array, the PROBLEM is here
console.log(elem[0].querySelectorAll('.widget-btn'));
elem.on('click', function(e){
// But when I click in the directive, it returns the elements that I want
console.log(angular.element(elem[0].querySelectorAll('.widget-btn')));
});
scope.$on(scope.widget, function(){
console.log('directive get the controller message');
});
var widgetId = controller.widget;
function socketInit(){
socket.forward(widgetId, scope);
scope.$on('socket:'+ widgetId, function(){
console.log('Get socket msg');
});
};
socketInit();
}
return {
restrict: 'EA',
controller: controller,
// Our controller $scope will change for vm
controllerAs: 'vm',
// We set our widget info in the datasource.
scope: {
// After the configuration our datasource is accesible in the vm.datasource
datasource: '=',
addchanneluser: '&',
widget: '#'
},
bindToController: true,
templateUrl: '/angular-js/views/dashboard/directives/small/small.template.html',
link: link,
}
console.log(elem), it gives me directive element DOM so what I want to do is find in that DOM, all the elements that has channel class. I execute with querySelector and it gives me empty array.
But when all the directive its charge in the browser and I click in the directive console call is with content.
My questions is, it can be some initialisation problem or querySelector is charged after the link function.
Thanks

Related

How to use directive-defined events to update my model

I am creating drag and drop functionality by creating a <dragItem> directive and a <droptTraget> directive, but I don't understand yet how to work with the inner and out scope in this way.
Here are my directives. The events triggers the functions properly, I just want the on dragstart event to store a value of the drag element and the drop event to trigger the function testAddSet() which adds the drag value to my model.
drag
angular.module('app.directives.dragItem', [])
.directive('dragItem', function(){
return { // this object is the directive
restrict: 'E',
scope: {
excercise: '='
},
templateUrl: "templates/dragTile.html",
link: function(scope, element, attrs){
element.on('dragstart', function (event) {
var dataVar = element.innerText;
// It's here that I want to send a dataVar to the $scope
});
}
};
});
drop
angular.module('app.directives.dropTarget', [])
.directive('dropTarget', function(){
return { // this object is the directive
restrict: 'E',
scope: {
day: '='
},
templateUrl: "templates/calDay.html",
link: function(scope, element, attrs){
element.on('drop', function (event) {
event.preventDefault();
// It's here that I'd like to take the value from the drag item and update my model
testAddSet() // doesn't work
$parent.testAddSet() // doesn't work
});
element.on('dragover', function (event) {
event.preventDefault();
});
}
};
});
Since you are using isolate scope, you need to define an attribute for the function binding.
angular.module('app.directives.dropTarget', [])
.directive('dropTarget', function(){
return { // this object is the directive
restrict: 'E',
scope: {
day: '=',
//Add binding here
testAddSet: '&'
},
templateUrl: "templates/calDay.html",
link: function(scope, element, attrs){
element.on('drop', function (event) {
event.preventDefault();
//Invoke the function here
scope.testAddSet({arg: value, $event: event});
});
element.on('dragover', function (event) {
event.preventDefault();
});
}
};
});
In your template, connect the function using the directive attribute.
<drop-target test-add-set="fn(arg, $event)" day="x"></drop-target>
For more information on isolate scope binding, see AngularJS $compile Service API Reference - scope.
I recommend that the event object be exposed as $event since that is customary with AngularJS event directives.
$event
Directives like ngClick and ngFocus expose a $event object within the scope of that expression. The object is an instance of a jQuery Event Object when jQuery is present or a similar jqLite object.
-- AngularJS Developer Guide -- $event
I think the easiest way to get your cross-directive communication is to make a scope variable on the host page and then pass it double-bound ('=') to both directives. That way, they both have access to it as it changes.

Directive That Injects a Modal $on Event in AngularJS

How do I create a directive that injects a fixed position modal or a directly underneath the element that the directive is attached to? So in the directive I would want scope.$on to trigger injecting directly below the element.
Here's my code so far:
angular.module('mobileDashboardApp')
.directive('httpErrorMessage', function (HTTPErrors) {
return {
template: '<div></div>',
link: function(scope, element, attrs) {
console.log("hello");
scope.$on(HTTPErrors.badRequest, function (event, args) {
// Inject template directly below 'element' <-- part I'm not sure how to do
});
}
};
});

Transclude share scope with directive template

Assume this directive:
<validation-errors field="someField">Some errors: {{errors}}</validation-errors>
I thought I could create the directive function simple as this:
return {
require: '^form',
restrict: 'E',
link: link,
scope: {
field: '#'
},
transclude: true,
template: '<span ng-show="errors" class="alert-danger" ng-transclude></span>'
};
function link(scope, el, attr, ctrl, transcludeFn) {
scope.errors = ctrl.Validator.getErrors(attr.field);
}
But since Transclusion is the process of extracting a collection of DOM element from one part of the DOM and copying them to another part of the DOM, while maintaining their connection to the original AngularJS scope from where they were taken. (from docs), the scope doesn't work like I thought it would.
So I tried this which works, except that the "Some errors" part is duplicated:
transcludeFn(function(clone, transScope) {
scope.errors = transScope.errors = ctrl.Validator.getErrors(attr.field);
el.append(clone);
});
It doesn't work if I remove el.append(clone);.
What's the best way to make the transcluded content share the directive template's scope?
If you want to create the errors using the directive, give something like this a try, I've updated the code so that it compiles the template as well, now working exactly as the ng-transclude directive would out of the box.
'use strict';
/* Directives */
angular.module('myApp.directives', []).
directive('errordirective', ['$compile',function($compile) {
return {
restrict: 'E',
scope: {
field: '#',
},
transclude: true,
//Create some error text
controller: function() {
this.errorText = "Some Errors We Created";
//Make the template available to the link fn, and make it an angular element.
this.template = angular.element('<span class="alert-danger" ng-transclude></span>')
},
//Make the controller available easily
controllerAs: 'Errors',
//Bind the scope properties to the controller, only works with controllerAs
bindToController: true,
template: '<span class="alert-danger" ng-transclude></span>',
link: function(scope, elem, attrs, ctrl, transcludefn) {
//Replace the original element with the new one that has the error text from our controller
transcludefn(scope, function(el) {
var template = ctrl.template;
var html = el.html();
//Add the transcluded content to the template
template.html(html);
//Compile the template with the appropriate scope
template = $compile(template)(scope);
//Replace with the new template
elem.replaceWith(template);
});
}
};
}]);

Angular remove Element

I have a directive:
app.directive('eventView', function () {
return {
restrict: 'E',
replace:true,
templateUrl: 'partials/EventView.html',
controller: 'eventController',
link: function(scope, element){
scope.$on("$destroy",function() {
element.remove();
});
}
}
});
The controller for the directive:
app.controller('eventController', function($scope, $rootScope){
$scope.removeEvent = function (){
$scope.$broadcast("$destroy")
}
});
There are several instances of the same directives in the rootscope. Each directive is a div in which there is a remove button which is bind to the removeEvent method in the controller definition.
The problem is that when a remove button is pressed in one those div(from eventView directive), all the divs are being removed. I only want the current directive from where the destroy is being broadcasted to be removed. I know its because i'm broadcasting $scope.$broadcast("$destroy") but how can I remove only the current scope, is there a predefined method only for that current scope
if you are using ng-repeat then you can use $index as the unique identifier for those div...
just update your code like below:
app.directive('eventView', function () {
return {
restrict: 'E',
replace:true,
templateUrl: 'partials/EventView.html',
controller: 'eventController',
link: function(scope, element){
scope.$on("$destroy" + identifier,function() {
element.remove();
});
},
scope: {identifier : '#'}
}
});
and broadcast the event with identifier
app.controller('eventController', function($scope, $rootScope){
$scope.removeEvent = function (identifier){
$scope.$broadcast("$destroy" + identifier)
}
});
if the divs are not part of ng-repeat, then you will need to have some kind of identifier to identify which div to destroy...

unit testing angularjs directive

I would like to start doing unit testing for my angularjs project. That's far from being straight forward, I find it really difficult. I'm using Karma and Jasmine. For testing my routes and the app dependencies, I'm fine. But how would you test a directive like this one ?
angular.module('person.directives', []).
directive("person", function() {
return {
restrict: "E",
templateUrl: "person/views/person.html",
replace: true,
scope: {
myPerson: '='
},
link: function (scope, element, attrs) {
}
}
});
How would I test for instance that the template was found ?
Here is the way to go https://github.com/vojtajina/ng-directive-testing
Basically, you use beforeEach to create, compile and expose an element and it's scope, then you simulate scope changes and event handlers and see if the code reacts and update elements and scope appropriately. Here is a pretty simple example.
Assume this:
scope: {
myPerson: '='
},
link: function(scope, element, attr) {
element.bind('click', function() {console.log('testes');
scope.$apply('myPerson = "clicked"');
});
}
We expect that when user clicks the element with the directive, myPerson property becomes clicked. This is the behavior we need to test. So we expose the compiled directive (bound to an element) to all specs:
var elm, $scope;
beforeEach(module('myModule'));
beforeEach(inject(function($rootScope, $compile) {
$scope = $rootScope.$new();
elm = angular.element('<div t my-person="outsideModel"></div>');
$compile(elm)($scope);
}));
Then you just assert that:
it('should say hallo to the World', function() {
expect($scope.outsideModel).toBeUndefined(); // scope starts undefined
elm.click(); // but on click
expect($scope.outsideModel).toBe('clicked'); // it become clicked
});
Plnker here. You need jQuery to this test, to simulate click().

Resources