I have the following directive that has two children divs. The second div (created with angular.element) should be clickeable. Since I created it with angular.element, I'm trying to add ng-click with the attr function, but this doesn't work. What's wrong with this code?
app.directive('mydir', function () {
var directive = {};
directive.restrict = 'EA';
directive.replace = true;
directive.scope = {
control: '=',
};
directive.template = '<div id="root"></div>';
directive.link = function (scope, element, attrs) {
var wrap = angular.element('<div id="wrap"></div>');
element.append(wrap);
var node = angular.element('<div id="node"></div>');
node.attr('ng-click', 'toggle()'); // <--- this doesn't work
wrap.append(node);
scope.toggle = function () {
alert('clicked');
};
});
return directive;
});
The element has to be compiled using angular's $compile service:
app.directive('mydir', function ($compile) { // added dependency here
var directive = {};
directive.restrict = 'EA';
directive.replace = true;
directive.scope = {
control: '=',
};
directive.template = '<div id="root"></div>';
directive.link = function (scope, element, attrs) {
var wrap = angular.element('<div id="wrap"></div>');
element.append(wrap);
var node = angular.element('<div id="node"></div>');
node.attr('ng-click', 'toggle()'); // <--- this doesn't work
var content = $compile(node)(scope);
wrap.append(content);
scope.toggle = function () {
alert('clicked');
};
});
return directive;
});
Here's a short tutorial on using $compile. Hope that helps
Related
This is part of my AngularJS application
.controller('Littlebear',function($scope) {
$scope.spread='<h1>this is the</h1> Littlebear spread.'+
'<img ng-src="src/images/retro.png" alt="picture" ng-click="click()">';
})
.directive('spread', function($compile) {
var templateTemp='<p> try again </p>';
var directive = {};
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, atttributes) {
// bind element to data in $scope
templateTemp=$scope.spread;
return templateTemp;
};
return linkFunction;
};
directive.restrict = 'E'; /* restrict this directive to elements */
directive.template = templateTemp;
return directive;
})
I would like to set template = $scope.spread inside the directory.
If I console.log the templateTemp inside the linkFunction the value of templateTemp is exacly what I am looking for but ouside of that function is templateTemp=' try again ';
can anyone suggest any solution?
(PS: as you might imagine I am quite new to AngularJS)
Thanks Vincent
In link function you can do something as below.
In link function, I have compiled the desired html and replaced the directive element with the same.
.controller('Littlebear',function($scope) {
$scope.spread='<h1>this is the</h1> Littlebear spread.'+
'<img ng-src="src/images/retro.png" alt="picture" ng-click="click()">';
})
.directive('spread', function($compile) {
var templateTemp='<p> try again </p>';
var directive = {};
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, atttributes) {
// you can change and compile the html like this
//element.replaceWith($compile($scope.spread)); //Please Check this line<-----
//Please try this.
var html =$scope.spread;
var e =$compile(html)($scope);
element.replaceWith(e);
};
return linkFunction;
};
directive.restrict = 'E'; /* restrict this directive to elements */
directive.template = templateTemp;
return directive;
})
How to call a method defined in child directive, within a button click listener of parent directive.
angular.module('editableDivDirective', [])
.directive("editableDiv", function() {
var directive = {};
directive.restrict = 'E';
directive.replace = 'true';
directive.scope = {};
directive.transclude = 'true';
directive.template =
'<div id="wrapper">' +
'<div required class="text-area" name="search" contenteditable="true" ng-model="formData.text"></div>' +
'<button type="submit" class="btn btn-warning add-button" id="submit" ng-click="createTodo()">Add</button>' +
'</div>';
directive.link = function(scope, element, attrs, controller) {
scope.createTodo = function(){
// do something
// Call child directive setPlaceholderText()
}
};
return directive;
})
.directive("contenteditable", function() {
var directive = {};
directive.require = ['^editableDiv','?ngModel'];
directive.restrict = 'A';
directive.scope = {};
directive.link = function(scope, element, attrs, ctrls) {
var ngModel = ctrls[1];
var editableDivController = ctrls[0];
function setPlaceholderText(){
return element.html("Hello World");
}
return directive;
})
I want to call child directive setPlaceholderText() when 'scope.createTodo()' of parent directive is called.
How can that be done.
In your parent directive
link: function($scope,$el,$attr) {
$el.find(".thing").on('click',function(event){
$scope.$broadcast('thing', $scope.someData);
});
}
In your child directive
link: function($scope. $el, $attr) {
$scope.$on('thing',function(event, someData) {
alert('My parent called me with this data: ' + someData);
});
}
You can $broadcast an event to child scopes (as mentioned), or you can also add a function to your parent directive's controller from your child directive.
Here is a jsfiddle with examples of both $broadcast to child directive and using the controller technique.
angular.module("myApp", [])
.directive("parentDirective", function () {
var directive = {};
directive.restrict = "E";
directive.scope = {};
directive.template = '<div>' +
'<button type="button" ng-click="broadcastDemo()">' +
'Broadcast to child' +
'</button>' +
'<button type="button" ng-click="controllerDemo()">' +
'Use Controller' +
'</button><br /><br />' +
'<child-directive></child-directive>';
//Setup directive controller
directive.controller = function ($scope) {
var ctrl = this;
//Store events for convenience
var events = ctrl.events = {
setPlaceHolderText: "setPlaceHolderTextEvent"
};
$scope.broadcastDemo = function () {
//$broadcast event and optional additional args
$scope.$broadcast(events.setPlaceHolderText, "Additional arg1", "Additional arg2");
};
$scope.controllerDemo = function () {
//do some work
//call the ctrl.setPlaceHolderText added by child
if (ctrl.setPlaceHolderText) {
ctrl.setPlaceHolderText();
}
};
};
return directive;
})
.directive("childDirective", function () {
var directive = {};
directive.restrict = "E";
directive.scope = {};
directive.require = ["^parentDirective", "?ngModel"];
directive.template = '<div></div>';
directive.link = function (scope, elem, attrs, ctrl) {
var parentDirCtrl = ctrl[0];
//allow parent scope(s) to $broadcast event
scope.$on(parentDirCtrl.events.setPlaceHolderText,
function (event, arg1, arg2) {
elem.html("$Broadcast: " + arg1 + " " + arg2);
});
//Add function to parent controller
parentDirCtrl.setPlaceHolderText = function () {
elem.html("Added to parent controller!");
}
};
return directive;
});
If you have multiple childDirectives and you go with adding function to parent directive controller, you will have to take precaution because each additional child directive would clobber the ctrl.setPlaceHolderText (i.e. only one elem.html would be called).
The bindonce library uses something similar to the second technique to allow child directives to add "binders" to the parent directive. The eventing model is probably cleaner for your purposes, but wanted to provide another option for directive communication.
Broadcast is not preferred, since If there is multiple instance of child or even parent child combination then broadcast will trigger all.
I have the following kendoDatePicker included in an AngularJS directive; the directive is supposed to show the picker opening the calendar but it doesn't. What's wrong with this code? This is the plunk.
HTML:
<dir2></dir2>
Javascript:
var app = angular.module("app", [ "kendo.directives" ]);
function MyCtrl($scope) {
}
app.directive('dir2', function() {
var directive = {};
directive.restrict = 'A';
directive.template = '<input kendo-date-picker="picker" />';
directive.link = function (scope, element, attrs) {
scope.picker.open();
};
return directive;
});
You should define directive.restrict = 'E' instead of 'A'.
'A' refers for attribute, 'E' for element.
app.directive('dir2', function() {
var directive = {};
directive.restrict = 'E';
directive.template = '<input kendo-date-picker="picker" />';
directive.link = function (scope, element, attrs) {
scope.picker.open();
};
return directive;
});
This is the solution:
where it says
scope.picker.open();
it should say
$timeout(function() {
scope.picker.open();
}, 1);
I want to add spyOn in a function defined inside the link function of a directive. I tried spyOn(element, function_name) after compiling the element, but it doesn't work.
Expose the function on the directive's scope.
function link (scope, element, attrs) {
// this can be spied on
scope.myFunction = function () {
};
// this won't be able to be spied on
function myPrivateFunction () {
}
}
Retrieve the directive's scope.
var directiveScope = angular.element('#myElement').scope();
// or .isolateScope() for isolate scope directives
Spy on the function.
spyOn(directiveScope, 'myFunction');
You can achieve this by using an isolateScope
//--directive.js--//
(function(angular) {
angular.module('module')
.directive('directive', directive);
directive.$inject = ['dv'];
function directive(dv) {
var directive = {
link: link,
replace: true,
restrict: "E",
scope: {
bindto: '=bindto'
}
};
return directive;
function link(scope, element, attrs) {
scope.generate = generate;
scope.generate();
function generate() {
/**
* Generate code goes here
**/
}
}
}
})(angular);
//--directive.spec.js--//
describe('Unit Test: Line-chart directive', directiveTest);
function directiveTest() {
var dvFactory;
var $compile;
var $scope;
var element;
var compiledElement;
var $isolateScope;
beforeEach(module('module'));
beforeEach(inject(function(_$compile_, _$rootScope_, _dv_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
dvFactory = _dv_;
element = '<directive></directive>';
//Compile the directive element
compiledElement = $compile(element)($scope);
//Obtain an isolateScope of the compiled element
$isolateScope = compiledElement.isolateScope();
}));
it('should call and spy on generate', function() {
//spyOn a generate() in $isolateScope
spyOn($isolateScope, 'generate').and.callThrough();
//Call the method
$isolateScope.generate();
//Check it is called
expect($isolateScope.generate).toHaveBeenCalled();
});
}
I want to unit test a directive which looks like this:
angular.module('myApp', [])
.directive('myTest', function () {
return {
restrict: 'E',
scope: { message: '='},
replace: true,
template: '<div ng-if="message"><p>{{message}}</p></div>',
link: function (scope, element, attrs) {
}
};
});
Here is my failing test:
describe('myTest directive:', function () {
var scope, compile, validHTML;
validHTML = '<my-test message="message"></my-test>';
beforeEach(module('myApp'));
beforeEach(inject(function($compile, $rootScope){
scope = $rootScope.$new();
compile = $compile;
}));
function create() {
var elem, compiledElem;
elem = angular.element(validHTML);
compiledElem = compile(elem)(scope);
scope.$digest();
return compiledElem;
}
it('should have a scope on root element', function () {
scope.message = 'not empty';
var el = create();
console.log(el.text());
expect(el.text()).toBeDefined();
expect(el.text()).not.toBe('');
});
});
Can you spot why it's failing?
The corresponding jsFiddle
Thanks :)
console.log((new XMLSerializer()).serializeToString(el.get(0)));
returns
<!-- ngIf: message -->
because you are using replace without a parent element in validHTML with combinaison of ng-if .So either you change validHTML and add a div as parent.
or
test your expectations on the next sibling of el
el.next()
which will be
<div ng-if="message" message="message" class="ng-scope"><p class="ng-binding">yes</p></div>