I try do something with directive in angular, but I've some problem with $compile function in programmatically html element, call here "phe".
var phe = angular.element('<div style="background-color:orange">{{value}}</div>');
When I append "phe" after or before the directive's element, it work like a charm...
var $phe = $compile(phe)(scope);
element.after($phe);
but if I wrapped the directive element with this "phe" the $compile not work.
element.wrap($phe);
Somebody have some idea?
I have made a plunker http://plnkr.co/edit/0x2MmQ7WYmiNog0IEzTj?p=preview
it works if you change the compilation sequence... compile the element before placing it in the dom
var phe_b = angular.element('<div style="background-color:orange"> b {{value}}</div>');
var $phe_b = $compile(phe_b)(scope);
element.before($phe_b);
do same for after...
The reason it doesn't work with wrap is because wrap clones the DOM element. In other words, if you did:
var wrapper = angular.element("<div>");
element.wrap(wrapper);
console.log(wrapper[0] !== element[0].parentNode); // this would output "true"
So, the element that you compiled/linked is not the same that ends up in the DOM.
You could, of course, get the wrapping element (it's the return value of wrap) and $compile it, but you need to be careful not to re-compile/re-link certain directives that were applied on the current element (including the very same directive) and its children.
Related
$scope.addNew = function(){
$('.thumb-img-gallary').append("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>");
}
I am calling this function to add element dynamically. But then delThumbImgGallaryPhoto() is not getting called.
you cannot just append an element with a ng-click or any other directive, and expect it to work. it has got to be compiled by angular.
explenation about compilation from angular docs:
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive
compelation happens in one of two cases:
When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements
when you call the $compile service inside an angular context
so what you need to do, is first to compile it(remeber to inject the $compile service in your controller), and then append it:
$scope.addNew = function(){
var elem = $compile("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>")($scope);
$('.thumb-img-gallary').append(elem);
}
BTW, remember it is prefable not to have any DOM manipulations done in the controller. angular has directives for that.
You have to compile it with scope
try like this
var item="<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>"
$('.thumb-img-gallary').append(item);
$compile(item)($scope);
angular doesn't know anything about your newly added element. You need to compile your newly added element using $compile. and you should better use directive for this task.
It is a bad habit to access ui elements from controller.
edit: it would be best using ng-repeat for this task. lets say you have a thumb-gallery directive which is repeated using ng-repeat by thumbs array.
when you need to add a new image you only need to add it to your thumbs array.
it is simple and straightforward
Your html would look like
<thumb-gallery ng-repeat="gallery in galleries"></thumb-gallery>
and your js would look like
var gallery = {};
$scope.galleries.add(gallery);
Within a unit test for an angular directive, I want to inspect the DOM that has been compiled:
var element = $compile("<div pr-sizeable='...'></div>")($rootScope);
var children = element[0].children; // HTMLCollection
expect(children.length).toBeGreaterThan(0); // that's fine
Especially, I want to check for the existence of a specific child element having an attribute pr-swipe-handler='right'.
I know I could iterate the children and their attributes-collections, but I'm sure there is a more tense solution.
Here's what I tried (refering to this answer of a similar post):
// TypeError: angular.element(...).querySelector is not a function
angular.element(children).querySelector("[pr-swipe-handler='right']")
// TypeError: children.querySelector is not a function
angular.element(children.querySelector("[pr-swipe-handler='right']"))
Here's a plunkr that should help you:
http://plnkr.co/edit/YRSgbsGCWGujhiOGV97z?p=preview
Code:
app.controller('MainCtrl', function($compile, $document, $scope) {
$scope.name = 'World';
var element = $compile('<div><p></p><div pr-swipe-handler="right"></div></div>')($scope);
console.log(element[0]);
console.log(element);
$document.append(element);
console.log(document.querySelectorAll('[pr-swipe-handler="right"]'))
});
You can not call querySelector on an element, but you can call it on document. You'll need to append the element to document in your tests, but you should be doing that anyway.
Peter Ashwell's answer showed me the right direction. Inspecting the HTMLCollection using the querySelector-api is possible only if the elements of the HTMLCollection are part of the actual DOM. Therefore it is necessary to append the compiled elements to it.
Thanks to that answer to a similar question, it turns out that the compiled element has to be appended to a concrete element in the DOM of the browser, such as body. $document is not enough:
var element = $compile('<div><p></p><div pr-swipe-handler="right"></div></div>')($scope);
var body = $document.find('body');
$document.append(element); // That's not enough.
//body.append(element); // That works fine.
var rightHandler = document.querySelectorAll('[pr-swipe-handler="right"]');
$scope.info = rightHandler.length + ' element(s) found.';
Refer to this plunk.
I am trying to implement a directive that that functions in a similar but not identical manner to ng-repeat. The details are not important, but at the end of the process I am attempting to pass a child scope to newly created DOM elements.
// Clone template element
var newItem = template.clone();
// Create new scope for element
var childScope = scope.$new();
// Pass relevant file into scope
childScope[indexStr] = member;
// Push new scope into element. <----- How to do this bit?
// Push element onto parent
template.parent().append(newItem);
// Clean up a bit
Naivly I tried:
newItem.scope = childScope;
but that doesn't work. (It just overrides the newItem.scope function to the best of my knowledge)
I also tried
newItem.scope() = childScope;
but apparently that is an illegal construct in JS.
The documentation is somewhat sparse on this, but I will gladly RTFM if someone can point me in the right direction.
I think you are looking for angular $compile functionality. You can use $compile provider to assign scope to a DOM element.
In your case do:-
$compile(newItem)(childScope);
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
I'm attempting to test an ng-if in one of my templates by compiling the view against a pre-defined scope and running $scope.$digest.
I'm finding that the compiled template is coming out the same regardless of whether my condition is truthy or falsy. I would expect the compiled html remove the ng-if dom elements when falsy.
beforeEach(module('templates'));
beforeEach(inject(function($injector, $rootScope){
$compile = $injector.get('$compile');
$templateCache = $injector.get('$templateCache');
$scope = $rootScope.$new();
template = angular.element($templateCache.get('myTemplate.tpl.html'));
}));
afterEach(function(){
$templateCache.removeAll();
});
it ('my test', function(){
$scope.myCondition = true;
$compile(template)($scope);
$scope.$digest();
expect(template.text()).toContain("my dom text");
// true and false conditions both have the same effect
});
Here's a plunkr attempting to show what's happening (not sure how to test in plunkr, so I've done it in a controller) http://plnkr.co/edit/Kjg8ZRzKtIlhwDWgB01R?p=preview
One possible problem arises when the ngIf is placed on the root element of the template.
ngIf removes the node and places a comment in it's place. Then it watches over the expression and adds/removes the actual HTML element as necessary. The problem seems to be that if it is placed on the root element of the template, then a single comment is what is left from the whole template (even if only temporarily), which gets ignored (I am not sure if this is browser-specific behaviour), resulting in an empty template.
If that is indeed the case, you could wrap your ngIfed element in a <div>:
<div><h1 ng-if="test">Hello, world !</h1></div>
See, also, this short demo.
Another possible error is ending with a blank template, because it is not present in the $templateCache. I.e. if you don't put it into the $templateCache exlicitly, then the following code will return undefined (resulting into an empty element):
$templateCache.get('myTemplate.tpl.html')
i have seen different example for rending an element in the link function
example one:
var template = '<span><input type="text" ng-model="ngModel"></span>';
element.html(template);
$compile(element.contents())(scope);
example two:
var template = '<span><input type="text" ng-model="ngModel"></span>';
element.html(template);
var el = $compile(element.contents())(scope);
element.replaceWith(el);
i had tried 2-3 simple directives which works even without replacing the element. so what is the use case for the "element.replaceWith(el)". When is it necessary to user "element.replaceWith(el)" at the end of the link function?
Replacement is actually optional, and the final result won't be exactly the same:
Your first example: the element with your directive has the span as its only child
Your second example: the element with your directive is finally replaced with the span -> one level less in the DOM.
All is about what you want in the DOM at the end. If you consider the original container with the directive is a useless wrapper only declaring a component, you will want to unwrap the content.