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
Related
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'.
I've been trying to track down a bug in ngGridv2.0.14 where the "gridID" class is not being set on my ngGrid classed element, even though the dynamic stylesheet is being appended to my document header, and I found something kind of wierd...
This bug causes the columns to appear "on top of" each other...
In ng-grid-2.0.14.debug.js there is a directive created called ngGrid (of course!). The directive uses the suggested "directive definition object" form. They define an inherited scope by defining scope:true and they declare a compile function which returns an object structure containing ONLY the function pre and DOES NOT declare the function post.
What is weird is that there is a return statement within this pre function, and I cannot find documentation about return statements within a pre function. The function ends up in Angular's "processQueue", which I believe could be Deffered.$$state.pending (I am tracing the call stack to find this, so I am not sure). During a $digest loop, angular tests these array indexes as functions and invokes them.
The ngGrid function that is being called, which can be found at ng-grid-2.0.14.debug.js line 3318, does a lot, and most of these things are working, such as $destroy cleanup logic, $complie($scope) of the gridTemplate, event listener establishing, and, most importantly, the addition of two very specific classes to our directive's iElement which appears to still be "in scope." This is the part that is failing.
They are adding a class to the iElement by:
iElement.addClass("ngGrid").addClass(grid.gridId.toString());
Sometimes this works, sometimes this doesn't...
Sometimes the element picks up the classes "ng-scope ngGrid ng[0-9]{13}" (e.g: ng-scope ngGrid ng1453913625179), and sometimes it does not, even when I have stepped through the function and watched the line of code that adds the classes get executed.
Further Investigation!: If I place an ng-if="true" on the parent node of my "ngGrid element" (the element that instanciates the ngGrid directive by attribute), then eveything works!
My Question: Does appending classes to an element within a $digest loop have issues or any reason why this would work sometimes and not all the time? Also, can you find documentation on setting return values for pre? I couldn't find anything about the return values here. Finally, why does setting ng-if="true" on the parent of iElement fix the issue of the class names not being appended to iElement's class list? Thanks!
I can't understand why angular ng-change function are called only in first click. In this example
http://jsfiddle.net/ZPcSe/5/
function is called every time when i change current radio selection, but in my code something is wrong
http://jsfiddle.net/4jL3u8ko/1/
Can someone change my code to work like first example, and explain why it isn't working?
Great question.
This is not an answer but one may call this explanations to answers and any further problems that may occur. And this happens quite often, so its important to understand. The answer given by #Victor is ok and the comment of #Atias is also helpful.
Explanation -
Simple explanation : ng-repeat forms child scope.
Little detailed :
So your application has 3 scopes (or may increase depending on the number of values in $scope.radioButtons). Let me name them - parentScope(main scope), childScope1(scope of first element of ng-repeat) and childScope2(scope of second element of ng-repeat).
Parent scope variables : calledFunctions(array), radioButtons(object) and newValue(function)
ChildScope1 variables : name(assigned a literal at the start), val(assigned an object at start) and value(it has no value, or undefined)
ChildScope2 variables : name(assigned a literal at the start), val(assigned an object at start) and value(it has no value, or undefined)
What happens when you click on the radio button of childScope1(for first time):
ChildScope1.value = ChildScope1.name or ChildScope1.value= "Radio1"; the ng-change directive checks if this scope's model has been changed? Yes, as it was undefined and now it has a literal("Radio1")!! So call ChildScope1.newValue(), which obviously is not present. Therefore now look for parent- call parentScope.newValue(). This is present, so execute it.
keep in mind that ChildScope1.value= "Radio1";
After clicking on other radio buttons......
Now lets click 2nd time on ChildScope1's radio button. After clicking it- ChildScope1.value = ChildScope1.name or ChildScope1.value= "Radio1"; the ng-change directive checks if this scope's model has been changed??? No!! Because it still contains the same literal vaule as before, so do not even look for ChildScope1.newValue() or ParentScope.newValue() function.
Now same thing happens with ChildScope2.
Hope this explains why your fiddle works like a one time binding, or how #Victor or #Atias are correct.
Simply the solution is - The ng-model of ChildScope1 or ChildScope2 or any other child scopes, should point to a variable of parent scope. So you can use $parent or make an object in parent scope(main scope) with a property( eg.- ParentVal={ChildVal = ""};). And in ng-model inside ng-repeat- write ng-model=ParentVal.ChildVal.
Sorry for my poor english and please pardon my spelling mistakes if there is any.
Thanks
ng-model is tricky. You should always, always have a dot in your models, or you get this kind of problem where you are creating several models and you think you only have one.
As a quick fix, use $parent.value instead of just value in your ng-model and ng-change.
As a good fix, have a proper model object in the scope, instead of storing values directly in the scope:
$scope.model = {};
ng-model="model.radiosValue"
As an even better fix, use the controllerAs pattern and use that object as the view model.
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);
});
}
};
})
I know two ways to bind HTML to client-side JavaScript code and stay a kind of object-oriented:
Use a lot of IDs (or special CSS class names, or some other distinct HTML attributes) in HTML and do a "harvest" in JS initialization method (or request each DOM object each time, right before use);
Do not write HTML at all. Construct an element at runtime, in initializer, and remember a reference to DOM object (or jQuery object) in a variable.
Are there some other ways that allow to use design-time phase (writhing HTML) which is much more convenient than doing all the work at runtime, and at the same time do not use a lot of identifiers of any kind having to maintain their uniqueness?
AngularJS is the framework you want to use for 2-way data binding.
I used AngularJS for multiple projects now, combined with nodeJS, and I never looked back at jQuery, you keep your code clean with the MVC pattern and manipulating the DOM is made easy and clear.
Example for 2 way data binding:
HTML
<p>{{elementText}}</p>
<input type="text" ng-model="item.value" />
JavaScript/Controller
$scope.item = {
value: ''
};
$scope.elementText = "The text you want to display";
console.log($scope.item.value); //Directly get your values from the scope.
Want to assign values to <select> boxes or fill <table>'s using JSON data? No problem, AngularJS got you covered.
Interesting AngularJS features:
ng-model
ng-repeat
Animations
Custom directives
I hope this will help you!