Angular 1 ng-class doesn't work as expected - angularjs

I have a simple ng-class that switches two classes based on the condition. When the class is switched, the order of the classes is messed up not sure why. Has anyone a solution for this?
<div class="ui" ng-class="{'two column grid' : submitNow, 'one column grid' : defaultState}"></div>
Rendered HTML when submitNow is true. This works as expected
<!-- submitNow is true -->
<div class="ui ng-scope two column grid"></div>
Rendered HTML when defaultState is true. This messes up the order of classes added by ng-class
<!-- defaultState is true -->
<div class="ui ng-scope column grid one"></div>
*** Edit ****
Quite strange because it works on jsfiddle. But here's the screenshot of my rendered html code
Here is a demo
https://codepen.io/vibwaj/pen/KKPBdNp

OK...looking at the style rules in elements inspector, semantic ui uses selectors like .ui[class*="two column"].grid > .row > .column
Not sure why they do it that way which is unusual and does make the order important.
Also not sure if it is angular or the browser that sorts the order of those classes. I suspect it is the browser, but that is a guess.
Rather than try to figure out what causes the sort you can add the following rule to fix layout for non specific class order.
.ui.two.column.grid > .row > .column,
.ui.two.column.grid > .column:not(.row){
width:50%!important;
}
Working codepen

Update:
I didn't notice the semantic UI framework that is using this approach.
If you still need the same approach, you can check the forked Codepen which I created a custom directive to be alternative than the original NgClass directive.
app.directive("myNgClass", function() {
return {
link: function(scope, element, attrs) {
scope.$watch(
function() {
return attrs.isExpand;
},
function(isExpand) {
element.removeAttr("class");
if (JSON.parse(isExpand)) {
element.addClass("two column grid");
} else {
element.addClass("one column grid");
}
}
);
}
};
});
Original Answer
So diving deep into the NgClass directive implementation in Angularjs source code and checking how they update the classes, there is a function called updateClasses.
In this function, it finds which classes should be removed and added.
Instead of replacing all the classes when the Boolean flag gets inverted, NgClass keeps the overlapping classes and checks which classes should be added / removed.
So in your case,one column grid (the default case) and two column grid have the column grid classes in common, so it will keep them, but remove the one from the start and add two at the end. So the result will be column grid one.
I really don't suggest to use the order of the classes as CSS selectors. This will make it more harder to select elements and make things more complex.
I also have a comment regarding the CSS selectors that you are using. I really suggest you to read Keep your CSS selectors short article so you can have a better practice of using shorter selector and why keeping the CSS selectors short can help with many things.
For example, If you don't need the one, column and grid classes seprately, you can just use .one-column-grid as a class name / CSS Selector instead of .one.column.grid.

Related

Using contenteditable with ng-model inside ng-repeat?

Here is my issue:
I am using ng-repeat to make a list of spans.
Each span has the contenteditable attribute and ng-model directive.
Everything works as expected (including two-way data binding), until I try to add a new item to the list.
<div ng-repeat="item in list">
<span ng-model="item.text" contenteditable></span>
</div>
<button ng-click="addItemToList"></button>
The methods look like this:
$scope.addItemToList = function () {
$scope.list.push({text: 'dummy text'});
}
or
$scope.addItemToList = function () {
$scope.list.splice(1, 0, {text: 'dummy text'});
}
When adding the new item to the list (push or splice), the DOM updates, but the last item is initialised empty, with no text whatsoever. The last item in the model list also empties out, even if I specifically push an element with text in it.
After a few tests, I've noticed that this only happens if the list's length is bigger after modifying it:
if I try to replace/modify/remove (not add) an element in the list, it works well.
I believe this has to do with the way contenteditable elements initialise in the DOM (I think they initialise empty, and somehow, the model empties out as well).
Has anyone encountered this problem before? If yes, how did you solve it / what workaround have you found?
Based on the angular docs related to ngModelController, it seems that there is not built-in support for two-way data binding with contenteditable elements, seeing as in the plunkr example they wrote their own contenteditable directive. You might be able to use a modified version of that as a workaround.
It looks to be a similar problem as this question and the contenteditable directive there looks similar to the contenteditable directive in the angular docs example.
I also found this directive on github that might accomplish what you are trying to do.
Edit: I did a new version of the plunk I posted in the comment above:
https://plnkr.co/edit/v3elswolP9AgWHDIPwCk
In this version I added a contenteditable directive that appears to be working correctly. It is basically a spin off of how the input[type=text] directive is written in angular, but I took out the places where it handles different types of input (since in this case it will just be text) and the places where it handles events that contenteditable elements don't even fire. I also changed it so that the $viewValue gets updated based on element.html() instead of element.val(). You might be able to use something like this as a workaround
The issue is old but that was the same problem for me today. (angular 1.5). My workaround was to add on blur update option: <td contenteditable data-ng-model="position.value" ng-model-options="{updateOn: 'blur'}"></td> somehow then model stopped getting be empty on initialize. I like using update on blur in many places (solves some performaces issues) so this is good for me, however it's some kind of trick.

Apply rowTemplate on Kendo grid without overriding current

I'm using Angular 1.4 typescript, with Kendo (using angular directives).
I'm trying to make a RowTemplate for each row, to change the color based on a property of the item.
I know there are some approaches with jQuery, but I find them very displeasing... If I'm using angular, I would like to reference items with angular.
This is my HTML:
<div id="resultSubTasksGrid"
kendo-grid="resultGrid"
k-options="vm.gridOptions"
k-columns="vm.columns">
</div>
This is my gridOptions:.
gridOptions: kendo.ui.GridOptions = {
rowTemplate : "<tr data- uid='#: uid #' ng-class='sent: item.IsSent'></tr>"
}
My problem comes here: I don't want to override the full row. This approach does so. I have lot of columns, and almost all of them have celltemplates I don't want to lose (but I don't want to have them all in the RowTemplate either).
I would like to know if is it possible to have something like:
rowTemplate : "<tr data- uid='#: uid #' ng-class='sent: item.IsSent'>{{RENDERCONTENT}}</tr>"
Well, it seems that by how Kendo it's developed, once you set up a row-template, you need to go all in. There is not such thing as partial template or wrapper.
More information here.

prevent angular from triggering filter function and binding function of directive for hidden elements

As far as I know angular doesn't link hidden HTML elements, so I assume when the viewport width is 500px or larger, the first paragraph is not linked (the mydirective link function is not triggered).
<p mydirective>{{content | customFilter}}<p>
<p mydirective>{{content}}<p>
p {
display:block;
}
#screen and min-width:500px {
p:nth-child(1) {
display:none;
}
}
However, the filtering function is still triggered as well as binding function for mydirective. Is there any way to prevent angular from triggering filter function and binding function for hidden HTML elements?
The task here for me is to show filtered data for small screens (less than 500px) and unfiltered data for larger screens.
Instead of using CSS to hide and show elements, use ng-if which will then remove the elements from the DOM and therefore prevent any Angular related magic happening for those elements (Until you want them back again).

AngularJS 1.2 - ngAnimate not working

I am new to using ng-animate with AngularJS 1.2. I am not sure why my ng-animate does not work a certain class name but works with the default for a simple fade in that I saw in an example.
In this example, I try to set my ng-animate class to be 'animation':
http://plnkr.co/edit/QWQUUVdcLmzLKRvVibqN?p=preview
but when I use the default, and my class name for animations is just ".ng-enter" and ".ng-leave", the fade in animation seems to work fine.
http://plnkr.co/edit/lEQhMwd6RWmsdmJbosu0?p=preview
Any help would be greatly appreciated, thanks!
The ng-animate attribute is deprecated in 1.2.
In 1.2 you define the appropriate CSS classes using a special naming convention. If you want a specific name like 'animation', you need to add that class to the element you want to animate.
As long as you have the correct CSS classes, some directives will be animated automatically. Which ones can be found here: http://docs.angularjs.org/api/ngAnimate
This is the reason your animation works in your second example when just defining the ".ng-enter" class. This would however automatically animate all directives that support the enter animation.
I have updated your first example to make it work with the class named 'animation':
HTML:
<li ng-repeat="item in items" class="animation">{{item}}</li>
CSS (Keeping selectors ungrouped for clarity):
.animation {
-webkit-transition: 1s;
}
.animation.ng-enter {
opacity: 0;
}
.animation.ng-leave {
opacity: 1;
}
.animation.ng-enter.ng-enter-active {
opacity: 1;
}
.animation.ng-leave.ng-leave-active {
opacity: 0;
}
Plunker: http://plnkr.co/edit/J0qJEMmwdzqXzu733fJf?p=preview
Also important to remember to add the animation module as a dependency to your module definition. Just in case anyone else is having problems getting animations working and hasn't done this.
angular.module('myApp', ['ngAnimate']);
You must check that the version of your angular.min.js matches with the version of angular-animate.min.js.
I got mine fixed this way.
As Tasse said ng-animate is deprecated, we need to use the class.
If you are copying CSS from angularjs web site http://www.nganimate.org/angularjs/ng-repeat/move then you need to modify those CSS in a particular format
For complete detail check Apply Angularjs Animation in 2 minutes
This is in addition to accepted answer, for those who are trying to animate an element with ng-show directive. These are styles which must be used:
.animation.ng-hide-remove {
transition:2s linear all;
opacity:0;
}
.animation.ng-hide-remove.ng-hide-remove-active {
opacity:1;
}
Please note, not all angular directives adds ng-enter, ng-enter-active, ng-leave and ng-leave-active. For example, ng-show directive adds ng-hide-remove at the beginning of animation and ng-hide-remove-active at the end of it. For more details follow this link:
https://www.w3schools.com/angular/angular_animations.asp

Stop AngularJS inserting <span class="ng-scope"></span> using ng-include

I'm using the Foundation layout framework, which automatically floats the last sibling of .column to the right and I really appreciate this is a behaviour. However, AngularJS takes it upon itself to insert span.ng-scope after every div.column, which somehow causes browsers to consider the last span the last sibling of .column (even though it is not).
Specifically the css in Foundation responsible for this is:
[class*="column"] + [class*="column"]:last-child { float: right; }
As I understand it, [attribute*="substring"] should select only siblings that match, so, for the above, only elements whose class attribute contains column (including columns). I would think a span tag whose class attribute that does not contain column should not match (and thus be ignored by :last-child). However, this does not seem to be the case.
Regardless, the span is causing the problem:
Angular buggering it up (jsfiddle)
Works fine without Angular (same jsfiddle, no ng-include)
Is there a way to configure angular to stop inserting those span tags? I would, begrudgingly, modify the css selector to somehow ignore all span tags; however I might eventually need/want to use a span tag.
Since you indicated the div can be moved inside, this works:
<ng-include src="'main.tmpl'"></ng-include>
Then in your template:
<div class="row">
<article id="sidepanels" class="four columns">
...
</div>
I'm not aware of any way to prevent angular from inserting the span tags (I think it keeps track of scopes that way -- for garbage collection).
Also you can try my version of include directive that does not creates a scope: Gist source.
As no scopes are created, AngularJS should not create additional element to mainain scope (it actually use data attributes to store link to scope).

Resources