I noticed something very strange about Angular 1.5.6 components. I have a component called scale. I call it:
<scale x-scale="xScale"></scale>
And in my controller:
$scope.xScale = 'lin'.
And my component definition:
angular
.module('myapp')
.component('scale', {
templateUrl: 'analyse/components/scales/scale.tpl.html',
controller: function(){
console.log('in controller and this is ', this);
},
bindings: {
xScale: '='
},
});
The console log outputs undefined.
But if i change x-scale to r-scale in my template and xScale in the binding to rScale, suddenly it works. In fact it seems that if i replace the x with any other letter, it works. Why is this?
It's in the documentation for directives
Normalization
Angular normalizes an element's tag and attribute name
to determine which elements match which directives.
We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel).
However, since HTML is case-insensitive, we refer to
directives in the DOM by lower-case forms, typically using
dash-delimited attributes on DOM elements (e.g. ng-model).
The normalization process is as follows:
Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _ -delimited name to camelCase.
So Angular strips of x- from the front of any attribute name to normalize it, this is done because both regular data-attributes, starting with data-, and x-attributes, starting with x- is valid in HTML 5.
The HTML5 specification states that
Attribute names beginning with the two characters "x-" are reserved
for user agent use and are guaranteed to never be formally added to
the HTML language.
It also states that
For markup-level features that are intended for use with the HTML
syntax, extensions should be limited to new attributes of the form
"x-vendor-feature", where vendor is a short string that identifies the
vendor responsible for the extension, and feature is the name of the
feature.
The x- attributes aren't used very often, but as noted above they are reserved for browser vendors, and you shouldn't be using them, instead you should be using data-attributes, where incidentally, Angular will also remove the data- part for you, so these
<scale data-scale="scale"></scale>
<scale x-scale="scale"></scale>
<scale scale="scale"></scale>
are all the "same" when you do
$scope.scale = 'lin'.
Related
Given:
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<a xlink:href="url"></a>
</svg>
</body>
Is it possible to use the HTML DOM's .querySelector() or .querySelectorAll() to select the link inside the SVG by the contents of its xlink:href attribute?
This works:
document.querySelector('a') // <a xlink:href="url"/>
These don't:
document.querySelector('[href="url"]') // null
document.querySelector('[xlink:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\\:href="url"]') // null
Is there a way of writing that attribute selector to make it 'see' the xlink:href?
Query selector can handle namespaces, but it gets tricky because
The syntax for specifying namespaces in CSS selectors is different from html;
The querySelector API doesn't have any method for assigning a namespace prefix (like xlink) to an actual namespace (like "http://www.w3.org/1999/xlink").
On the first point, the relevant part of the CSS specs allows you to specify no namespace (the default), a specific namespace, or any namespace:
#namespace foo "http://www.example.com";
[foo|att=val] { color: blue }
[*|att] { color: yellow }
[|att] { color: green }
[att] { color: green }
The first rule will match only elements with the attribute att in the "http://www.example.com" namespace with the value "val".
The second rule will match only elements with the attribute att regardless of the namespace of the attribute (including no namespace).
The last two rules are equivalent and will match only elements with the attribute att where the attribute is not in a namespace.
See this fiddle, paying attention to the fill styles (default, hover, and active):
https://jsfiddle.net/eg43L/
The Selectors API adopts the CSS selector syntax, but has no equivalent to the #namespace rule for defining a namespace. As a result, selectors with namespaces are not valid but the wildcard namespace token is valid:
If the group of selectors include namespace prefixes that need to be resolved, the implementation must raise a SYNTAX_ERR exception ([DOM-LEVEL-3-CORE], section 1.4).
This specification does not provide support for resolving arbitrary namespace prefixes. However, support for a namespace prefix resolution mechanism may be considered for inclusion in a future version of this specification.
A namespace prefix needs to be resolved if the namespace component is neither empty (e.g. |div), representing the null namespace, or an asterisk (e.g. *|div), representing any namespace. Since the asterisk or empty namespace prefix do not need to be resolved, implementations that support the namespace syntax in Selectors must support these.
(bold added)
Check out the fiddle again, this time paying attention to the console output. The command document.querySelector('[*|href="#url"]') returns the element you want.
One final warning: MDN tells me that IE8- do not support CSS namespaces, so this might not work for them.
Update 2015-01-31:
As #Netsi1964 pointed out in the comments, this doesn't work for custom namespaced attributes in HTML 5 documents, since HTML doesn't support XML namespaces. (It would work in a stand-alone SVG or other XML document including XHTML.)
When the HTML5 parser encounters an attribute like data:myAttribute="value" it treats that as a single string for the attribute name, including the :. To make things more confusing, it auto-lowercases the string.
To get querySelector to select these attributes, you have to include the data: as part of the attribute string. However, since the : has special meaning in CSS selectors, you need to escape it with a \ character. And since you need the \ to get passed through as part of the selector, you need to escape it in your JavaScript.
The successful call therefore looks like:
document.querySelector('[data\\:myattribute="value"]');
To make things a little more logical, I would recommend using all lower-case for your attribute names, since the HTML 5 parser will convert them anyway. Blink/Webkit browser will auto-lowercase selectors you pass querySelector, but that's actually a very problematic bug (in means you can never select SVG elements with mixed-case tag names).
But does the same solution work for xlink:href? No! The HTML 5 parser recognizes xlink:href in SVG markup, and correctly parses it as a namespaced attribute.
Here's the updated fiddle with additional tests. Again, look at the console output to see the results. Tested in Chrome 40, Firefox 35, and IE 11; the only difference in behavior is that Chrome matches the mixed-case selector.
[*|href] will match both html href and svg xlink:href, then use :not([href]) to exclude html href.
document.querySelectorAll('[*|href]:not([href])')
tested in chrome
Unfortunately not.
querySelector doesn't handle XML namespaces, so there is no easy way to do this that way. You can however use an XPath query.
var result = document.evaluate(
// Search for all nodes with an href attribute in the xlink namespace.
'//*[#xlink:href="url"]',
document,
function(prefix){
return {
xlink: "http://www.w3.org/1999/xlink"
}[prefix] || null;
},
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
var element = result.iterateNext();
If you need full cross-browser support, such as for IE, which does not have a document.evaluate, you can polyfill it with wicked-good-xpath.
Of course, depending on your usage, it may be easier to do this (which I think will work on IE):
var element = Array.prototype.filter.call(document.querySelectorAll('a'),
function(el){
return el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') === 'url';
})[0] || null;
I'm using ng-tranlate for i18n.
I'd like to combine a translated label together with a variable resolved from controller binding. How can I achieve the following?
<div translate="my.lang.text">some more: {{controller.attribute}}</div>
This does not work, and ng-translate ignores any content between the divs. why?
The translate directive will replace the content of the element with the translation you pass to it.
The use case you are describing looks like a parameterized translation. If you want to keep the use of the directive, you could pass the variable through the translate-values directive:
<div translate="my.lang.text"
translate-values="{value: 'some more: ' + controller.attribute}"></div>
You have to specify that your translation is parameterized:
JSON
"my.lang.text": "This is a parameterized string {value}"
I believe the translate directive replaces all the element's content with the translation.
In this case, you probably want to use the translate filter instead.
<div>{{'my.lang.text' | translate}} some more: {{controller.attribute}}</div>
As an alternative, you could also avoid this issue by giving the translated value it's own element.
<div><span translate="my.lang.text"></span> some more: {{controller.attribute}}</div>
If the translation is always intended to have have a value appended to it, then using a parameterized translation is probably the best solution (as suggested by Michael https://stackoverflow.com/a/33419608/90305)
I'm new in AngularJS, using it for two months in a project. I've learned how to use directives and theirs scopes (false, true, obj literal), but there's some questions about it...
First of all, we have some ng-repeats and directives with some behaviors, I tried to present a equivalent scenario in this link.
I didn't figure out how to access a function (testfn - within ng-controller) inside a directive child of another directive myItemDirective. But in myStepDirective it's accessible, I tried to pass it like in the first "layer" but didn't work.
PS.1: I created a myStepDirective with a isolated scope for other examples, if you need, just uncomment to test. Both I got a way to access params/functions from parent (controller), but not inside a grandchild.
Why directive's scope params doesn't work with camel case params? I don't remember to read some hint in AngularJS docs... typewithnocase inside myItemDirective works but typeList not.
Thanks!
EDITED]
For your 1. Here is a working fiddle working with limited scope and camel to snake case conversion : https://jsfiddle.net/wu0avqau/
I spend a loooooong time not understanding why it didn't worked but you juste forgot a = in your ng click inside your second directive
ng-click"testfn()"
For your 2. I can refer you to the documentation : https://docs.angularjs.org/guide/directive
Normalization
Angular normalizes an element's tag and attribute name to determine which >elements match which directives. We typically refer to directives by their case->sensitive camelCase normalized name (e.g. ngModel). However, since HTML is case->insensitive, we refer to directives in the DOM by lower-case forms, typically >using dash-delimited attributes on DOM elements (e.g. ng-model).
The normalization process is as follows:
Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _-delimited name to camelCase."
basicaly your myItemDirective would be my-item-directive inside your template but still be myItemDirective inside your js.
Good luck,
Thibaud Lamarche
I'm trying to compile some HTML on my controller like so:
$scope.online = networkService.isOnline();
var wrapper = angular.element(document.createElement("i"));
var compiledContents = $compile($scope.chapter.contents);
var linkedContents = compiledContents($scope);
wrapper.append(linkedContents);
$scope.chapter.linkedContents = wrapper.html();
Where the HTML being compiled has a few elements with a ng-if='online' there. But the compiled HTML always comes out with those elements commented, as if online was not true (which it is - I even got to the point where I added a console.log(scope.online) in Angular's $compile function and it printed true).
Am I missing anything here?
wrapper.html() gives back a string representing the inner HTML of the element. You are then assigning that string to $scope.chapter.linkedContents. Although it is not clear from your question how and where you are actually using $scope.chapter.linkedContents, one thing is certain - this string is definitely not "linked" in any way.
The actual "linkedContents" are the elements that should end up in the DOM. In your case, it is the wrapper element with its contents, but again - unclear, how, if ever, it ends up in the DOM.
But you shouldn't even be dealing with DOM in a controller. Controllers are DOM agnostic, so right there you should see a big warning sign that you are doing something wrong. Make sure that you understand the role of a controller, a directive, etc...
I think I understand what the problem you are trying to solve. You get some dynamic uncompiled HTML (or actual elements) - i.e. $scope.chapter.contents and you need to have it placed in the DOM and compiled/linked.
Typically, to bind HTML one would use ng-bind-html (assuming it's either trusted or sanitization is on):
<div ng-bind-html="chapter.contents">
</div>
But this would not be $compiled. To compile, I'd suggest writing your own directive that would work similar to ng-bind-html, but would also compile it:
<div compile-html="chapter.contents">
</div>
Then, the directive would take the content, compile/link it against some scope (say, child scope) and append it to the element hosting the directive compileHtml.
.directive("compileHTML", function($compile, $parse){
return {
scope: true,
link: function(scope, element, attrs){
// get the HTML content
var html = $parse(attrs.compileHtml)(scope);
element.empty();
// DISCLAIMER: I'm not dealing with sanitization here,
// but you should keep it in mind
$compile(html)(scope, function cloneAttachFn(prelinkContent){
element.append(prelinkContent);
});
}
};
})
as Web Components, I mean AngularJs's directives here.
I am trying to use special characters as a tag's name, especially asiatic ones (korean even more specifically).
Here's a plunker so you'll get a better grasp on what I try to achieve.
// library
(function (angular) {
angular.module('molecules', [])
.directive('헐', function () { return {
template: 'ㅎㅓㄹ'
}});
})(window.angular);
// main module
(function (angular) {
angular.module('lab', ['molecules']);
})(window.angular);
<div ng-controller="monitor1">
<헐></헐>
</div>
It seems to have to do with how the browser is interpreting the DOM. If you append an alphabet character to the foreign character (in the directive and the start and end tags), it works as expected i.e.
<a헐></a헐>
Note - check out the DOM while you are at it (I was checking in IE11) - notice that the tag is closed with . With <헐> notice that the IE has taken it upon itself to make some modifications. There is also the console error HTML1407: Invalid tag name. First character should match [a-zA-Z].