I'm trying to create a simple directive to avoid having bulky elements for hovering. This is what I have:
app.directive('ngHover', function(){
return {
compile: function($element,attr) {
$element.attr('ng-mouseenter',attr.ngHover + ' = true')
.attr('ng-mouseleave',attr.ngHover + ' = false')
.removeAttr('ng-hover');
}
}
})
The resulting element is what I would have wrote (and would have worked) but it doesn't seem to be added before angular uses $compile. I could use $compile manually but I want to understand why this doesn't work.
DEMO
This is how the compiler of Angular works.
Compiler is an Angular service which traverses the DOM looking for
attributes. The compilation process happens in two phases.
Compile: traverse the DOM and collect all of the directives. The
result is a linking function.
Link: combine the directives with a scope and produce a live view...
Which means that by the time when you are inside of compile function where you add the attributes, it's over and compiler will never discover and recognise ng-mouseenter and ng-mouseleave as directives. In order to achieve this you will need to trigger another round of compilation with $compile, as you said.
Also see this message and the whole thread. There you can see that it would work if you were setting extra directives on children of the current element, but not on itself.
Related
I would like to do something like this:
<a <myDir myVar="someInput"></myDir>>Call myDir</A>
However I'm realising you can't nest a directive within an a tag and set parameters this way.
I'm actually using jade though. I call the directive fine with.
a(myDir) Call myDir
but am unsure how to pass the variable to the directive. I've tried the equivalent jade to the above html, which is this:
a(myDir(myVar="someInput")) Call myDir
As its an optional parameter I use the '=?' syntax in the directive.
However the issue is definitely the syntax for calling the function in jade.
So after much failure this is how you do it.
// In the HTML/Jade
a(myDir info="someInput")
// In the Directive
scope: {
info: '#?info'
}
Check out this question and #aifarfa's answer for an example. Though the question is different it's a perfect example of what I wanted to do.
How to call directive from template html in angularjs
Previously I've put working webcam code into my application, but now it's not working when I updated to AngularJS v1.5.0. I am using webcam-directive which was working perfectly with v1.3.0.
Here is my code:
<webcam placeholder="selfiePlaceHolder"
on-stream="onStream(stream)"
on-access-denied="onError(err)" on-streaming="onSuccess(video)">
</webcam>
But now it's giving following error with AngularJS v1.5.0:
Uncaught Error: [$parse:isecdom] Referencing DOM nodes in Angular expressions is disallowed! Expression: onSuccess(video)
http://errors.angularjs.org/1.5.0/$parse/isecdom?p0=onSuccess(video)
I also tried to use a different solution with AngularJS ng-Camera but even its demo page is not working for me.
Note: I know the issue is that we can't access the DOM from the newer version of AngularJS, but the same code works with the older version. I need to know how to pass the "Video" DOM object to the controller.
I've found the solution to the problem. Two things need to be done:
First In HTML:
<webcam channel="channel"
on-streaming="onSuccess()"
on-error="onError(err)"
on-stream="onStream(stream)"></webcam>
Secondly, in the controller, you can access the DOM video with the following code:
$scope.onSuccess = function () {
// The video element contains the captured camera data
_video = $scope.channel.video;
$scope.$apply(function() {
$scope.patOpts.w = _video.width;
$scope.patOpts.h = _video.height;
//$scope.showDemos = true;
});
};
Here is a working example.
It is a potential error generally occurs when an expression tries to access a DOM node since it is restricted accessing to DOM nodes via expressions by AngularJS because it might cause to execute arbitrary Javascript code.
The $parse:isecdom error is related to an invoke to a function by event handler when an event handler which returns a DOM node, like below:
<button ng-click="myFunction()">Click</button>
$scope.myFunction = function() {
return DOM;
}
To fix this issue, avoid access to DOM nodes and avoid returning DOM nodes from event handlers. (Reference: https://docs.angularjs.org/error/$parse/isecdom)
Adding an explicit return might solve this issue as detailed here: CoffeeScript - Referencing DOM nodes in Angular expressions is disallowed
I was able to get webcam-directive working using the channel suggestion from the comment above, based on the example on the github page.
function MyController($scope) {
$scope.myChannel = {
// the fields below are all optional
videoHeight: 800,
videoWidth: 600,
video: null // Will reference the video element on success
};
}
In the onSuccess(on-streaming attr) and onStream(on-stream attr) callback the video property of myChannel was filled in with the video DOM element (and then it would obviously be available to everything else in the controller too). According to the comment in the example code though, you should wait to access it at least until onSuccess. Here is a working example
I'm trying to implement directive with dynamic controller so that I can bind controller based on some condition just like Todd Motto has shown it here
Everything works fine expect that I can't send object property as a name to the directive like,
<directive-with-dynamic-controller ctrl="someObj.prop"></directive-with-dynamic-controller>
I've even tried this, but to no avail:
<directive-with-dynamic-controller ctrl="{{someObj.prop}}"></directive-with-dynamic-controller>
It gives error like this:
Argument is not a function, got undefined
Any ideas how I can solve this? Or any other way?
Thanks!
Any ideas how I can solve this? Or any other way? Thanks!
The problem is in the order of execution. Something that was left out in the mentioned article is the fact that you cannot pass an expression in the case of setting a "dynamic" controller (so much for dynamic).
If we look at the notation of a directives compile step, you would notice that there is no access to the current $scope.
This is because DOM compilation and controller initialisation happens before the angular parser kicks in and evaluates your expression(s).
As such, you cannot pass a $scope expression into the ctrl attribute, as it is simply a regular DOM attribute at this point in time. In essence, you are passing the raw string into the ctrl attribute.
<my-custom-dir ctrl="foo.bar"></my-custom-dir>
// Error: "foo.bar" is not a controller // is not a function // $minErrObscureStuffThatDoesnHelpYou.
I've been trying to figure out a slick way to get deferred directive compilation running for some time now, but to no avail...
One possible way of getting around this problem (ymmv):
.directive('...', function ($controller) {
controller: function ($scope, $element, $attrs) {
$attrs.$observe('ctrl', function (n, o) {
return $controller(n, {
$scope: $scope,
$element: $element,
$attrs: $attrs
});
});
}
});
Effectively, you would replace a pre-initialised controller (that does nothing) with the controller matched by the name you passed through your attrs.ctrl attribute. However, this would execute post-compilation - so I wouldn't seriously recommend it.
jsfiddle showing the order of execution
tl;dr There is currently no slick way to define a controller for a directive, based on a $scope expression. It has to be a raw string because compilation isn't scoped on a per-component basis, but more so in a 'global' order of execution.
DOM Compilation > Controller initialisation > Scope linkage fiddle
I am having a little trouble hiding an element. I am attempting to hide this element using an AngularJS service. My code is as follows:
app.service('testService', function(){
var testElement = $("#testElement");
this.hideElement = function(){
testElement.hide();
}
});
The code above does not actually hide the element, but the following code does:
app.service('testService', function(){
this.hideElement = function(){
var testElement = $("#testElement");
testElement.hide();
}
});
However, I have multiple functions that use the testElement and I would hate to have to keep declaring it in all the functions that need testElement within the service. Am I doing something wrong here?
Am I doing something wrong here?
Yes. In fact your very first step was wrong. I mean having service that makes some DOM manipulations, in your case hiding HTML node. Services are data manipulation layer (retrieve, transform, save, post, etc.) but never presentation one, it should not care about View. Services are reusable piece of application code, meaning that it is supposed to be injected in different places of the app to provide a bridge to data sources, it should not make any view transformations, it's just not what they are for.
You should use directive for this with controller as mediator to decide when and what to hide and show. Most likely it will be enough to use build-in ngShow/ngHide directives with some boolean flags set in controller.
for html manipulation better to use angular controllers or inbuilt directives. services are never recommended.
If you really want to cache something, use simple JS Constants or html5 localstorage if you cache session wise use sessionstorage, they are really helpfull. or in angular $rootscope variables are also global.
Yes. What actually happened when you assign 'testElement' outside the hide method was 'testElement' will be assigned with undefined value.Since injection are created before the dom was available.So the below code doesn't work.
var testElement = $("#testElement");
this.hideElement = function(){
testElement.hide();
}
For DOM manipulation it is better to go with directives than services.
Bit of a short question, but wondering why this is happening:
In the example posted below, there are two SVG elements, each containing a directive assigned to a element. The first directive doesn't use a template, and is the setup I currently use to assign actions to paths.
The second directive is what I was trying to do in order to clean up my code a bit, but it won't be drawn.
I googled a bit around, and read there is a difference between SVG & DOM nodes? But since both ng-repeat's are working, I'm a bit puzzled on what is going on.
Link to plunkr: http://plnkr.co/edit/cok6O58SOUyaGHG5Jtu5?p=preview
angular.module('app', []).controller("AppCtrl", function($scope) {
$scope.items = [1,2,3,4];
}).directive('svgPathNoReplace', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
}
}
}).directive('svgPathReplace', function() {
return {
restrict: 'A',
replace: true,
template: '<path id="1" d="M 85.750001,0 C 38.393651,0 0,39.02444 0,87.15625 c 0,48.13181 38.393651,87.1875 85.750001,87.1875 C 133.10635,174.34375 171.5,135.28806 171.5,87.15625 171.5,39.02444 133.10635,0 85.750001,0 z m 0,1.5 C 132.28206,1.5 170,39.8217 170,87.15625 c 0,47.33455 -37.71794,85.6875 -84.249999,85.6875 C 39.217941,172.84375 1.5,134.4908 1.5,87.15625 1.5,39.8217 39.217941,1.5 85.750001,1.5 z" '+
'style="stroke:#000000;stroke-width:1px;"></path>'
}
});
The most current beta (1.3) allows another property on the directive called type where you can specify svg content now.
ie.
return {
restrict : 'AE',
type : 'svg',
template : '<path></path>'
link : function (..)
}
Your initial research was correct -- in AngularJS, templates are rendered as DOM nodes, not SVG nodes. This means the directive's template is rendering your <path> tag as a "dummy" non-standard HTML tag that does nothing.
In other words, although HTML normally recognizes path nodes as non-standard nodes, it's preconfigured to know how to handle them using SVG. But when HTML sees a non-standard DOM node rendered by the template (which in this example just happens to be named path), it doesn't recognize it and therefore does nothing. Think of it as trying to use <foo></foo>.
However, this doesn't mean the ng-repeat directive won't work on the custom DOM node, since it doesn't care what type of node (i.e., standard or custom) it is repeating.
Updated Plunker
The above example solves your problem by manually creating the HTML node using the directive's link function instead of its template key. The helper function creates the path node in a way that can be recognized by SVG, which eliminates the need to use a template.
My answer is inspired by this solution.