I'm creating a directive which position children div justify and with a margin = 0 changing the height of the children div. (like http://brunjo.github.io/rowGrid.js/)
All is working well. Except when the children are changing.
I have added a watch for the number of children so that if I add a child, it is still working.
But how to watch at the size of each child to update the layout when they are changing? Put a watch on each one of them?
Here is a plunker: http://plnkr.co/edit/OYZ1snIjmLS4HRB6J5vy?p=preview
I'm new to angular, it may be an easy answer.
Thanks in advance.
NOTICE:
Watch expressions (e.g. the first argument of a $watch() function call) are executed several times per $digest cycle. Therefore, they should be kept as quick as possible. If your element has many children, it might become expensive to calculate all their widths.
If that is the case, it might be better to capture the events that are associated with the resizing of the children and check the sizes only when there is indeed the possibility that something got resized.
That said, you could do something like this:
$scope.$watchCollection(function() {
return [].map.call(el[0].children, function (child) {
return child.offsetWidth;
});
}, function(newValue, oldValue) { // Note: newValue comes before oldValue
if (oldValue !== newValue) {
console.log('eoke');
this.layout(el, 'blue');
}
}.bind(this));
See, also, this short demo.
Related
I would like to disable the drag down to close gesture of mdbottomsheet. I've found a work around on scripts but I'm not sure where to put the code. Thanks for the help.
As you say that angular-material doesn't provide any option to disable it, obviously you will have to make changes in its source code.
Now, you haven't mentioned whether you want to disable it at specific places or turn drag-down-to-close for bottomSheets everywhere.
1) In case of latter, it would be quite straightforward, as the only thing you need to do is remove the event listeners for drag events.
If you use angular-material.js file, heres what you can do:
Find the function BottomSheet(element, parent). This function basically registers the drag events which close the sheet. We need make it not attach the listeners.
Reduce it to:
function BottomSheet(element, parent){
return {
element: element,
cleanup: angular.noop
};
}
The cleanup function basically de-registers the listeners on drag event.This function is called when the scope of the bottomSheet is destroyed. To make minimal changes, we have just reduced the cleanup function to do nothing.
2) If you want to be able to pass an option while creating the sheet in your controller, you do the same thing, but conditionally based on the option you pass. Wont write the code because I assume you know how angular works, but here are the steps:
=> Add a boolean variable along with other options(template,scope,etc. ). Lets call it dragDownToClose.
=> In the defaults injector function inside the provider function of MdbottomSheet , assign it a default value (true/false).
=>Pass this along with element and parent during instantiation of BottomSheet() inside the onShow function.
=> So BottomSheet() will now have three argument - dragDownToClose being the new one.
=> As we did in the former case, return the element without any handler attached when the value is false, and let the original function be when its true.
Of-course there are various ways in which you can actually implement this. However, I hope you get the idea.
First, inject $element into your controller. You known what AngularJS $element do, right?
Then we both known that the drag events are registered in BottomSheet
parent.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);
So, the simple solution is: Remove those events, override those events... without override the function BottomSheet, right?
$element
.on('$md.dragstart', function (ev) {
return false;
})
.on('$md.drag', function (ev) {
return false;
})
.on('$md.dragend', function (ev) {
return false;
});
Something still wrong here! The backdrop still draggable! So, we do the same for backdrop
var parent = $element.parent();
var backdrop = parent.find('md-backdrop');
backdrop
.on(blah blah blah
These is code in case you are asking for
You can try
$mdBottomSheet.show({
template: *yourTemplate*,
clickOutsideToClose:false
})
this will not let the user close the bottom sheet even with drag or click outside.
I not sure how to solve this issue. I am sure someone will know this very quickly.
I have an array of objects and modifying a property. I have a firebase listener 'child_changed'. When firebase is updated need to update the array. Here is the code below.
dbRefList.on('child_changed', function(snap) {
var len = this.grocerylist.length;
for(var i=len; i--;) {
if(this.grocerylist[i].key === snap.key) {
this.set(['grocerylist', i, 'selected'], snap.val().selected);
}
}
this.notifyPath('grocerylist', this.grocerylist.slice());
}.bind(this));
When the array is modified I want the template repeat-dom to trigger. I know this.set will not trigger array mutation sub properties but again I am not sure how to solve this. I done research and tried so many solutions.
I can force a render on the template dom-repeat but I would prefer the data binding way.
So this code (just the this.set you have in there now) should cause the value of grocerylist.i.selected to update inside the dom-repeat (assuming it's bound in there so it's actually showing up).
What behavior are you seeing? Are you trying to filter or sort the list based on the selected value? In that case, you might need to add observe="selected" on the dom-repeat.
(Also—have you confirmed that the child-changed callback is being called with the this value you expect—the element—rather than window or something else?)
You should be able to force a refresh by doing this.grocerylist = this.grocerylist.slice() or this.set('grocerylist', this.grocerylist.slice()); ... notifyPath doesn't work here because notifyPath doesn't change the value, it notifies the element about a prior change (the second argument is effectively ignored).
I'm looking for a pure angularJS way to call a controller method once a particular dom element is rendered. I'm implementing the scenario of a back button tap, so I need to scroll to a particular element once it is rendered. I'm using http://mobileangularui.com/docs/#scrollable.
Update: how my controller looks like:
$scope.item_ready=function(){
return document.getElementById($scope.item_dom_id).length;
};
$scope.$watch('item_ready', function(new_value, old_value, scope){
//run once on page load, and angular.element() is empty as the element is not yet rendered
});
Thanks
One hack that you could do and I emphasize hack here but sometimes it's just what you need is watch the DOM for changes and execute a function when the DOM hasn't changed for 500ms which is accepted as a fair value to say that the DOM has loaded. A code for this would look like the following:
// HACK: run this when the dom hasn't changed for 500ms logic
var broadcast = function () {};
if (document.addEventListener) {
document.addEventListener("DOMSubtreeModified", function (e) {
//If less than 500 milliseconds have passed, the previous broadcast will be cleared.
clearTimeout(broadcast)
broadcast = $window.setTimeout(function () {
//This will only fire after 500 ms have passed with no changes
// run your code here
}, 10)
});
}
Read this post Calling a function when ng-repeat has finished
But don't look at the accepted answer, use the 3rd answer down by #Josep by using a filter to iterate through all your repeat items and call the function once the $last property returns true.
However instead of using $emit, run your function...This way you don't have to rely on $watch. Have used it and works like a charm...
I'm trying to figure out why my $watch isn't being triggered. This is a snippet from the relevant controller:
$scope.$watch('tasks', function (newValue, oldValue) {
//do some stuff
//only enters here once
//newValue and oldValue are equal at that point
});
$scope.tasks = tasksService.tasks();
$scope.addTask = function (taskCreationString) {
tasksService.addTask(taskCreationString);//modifies tasks array
};
On my view, tasks is clearly being updated correctly as I have its length bound like so:
<span>There are {{tasks.length}} total tasks</span>
What am I missing?
Try $watch('tasks.length', ...) or $watch('tasks', function(...) { ... }, true).
By default, $watch does not check for object equality, but just for reference. So, $watch('tasks', ...) will always simply return the same array reference, which isn't changing.
Update: Angular v1.1.4 adds a $watchCollection() method to handle this case:
Shallow watches the properties of an object and fires whenever any of the properties change (for arrays this implies watching the array items, for object maps this implies watching the properties). If a change is detected the listener callback is fired.
Very good answer by #Mark. In addition to his answer, there is one important functionality of $watch function you should be aware of.
With the $watch function declaration as follows:
$watch(watch_expression, listener, objectEquality)
The $watch listener function is called only when the value from the current watch expression (in your case it is 'tasks') and the previous call to watch expression are not equal. Angular saves the value of the object for later comparison. Because of that, watching complex options will have disadvantageous memory and performance implications. Basically the simpler watch expression value the better.
I would recommend trying
$scope.$watch('tasks | json', ...)
That will catch all changes to the tasks array, as it compares the serialized array as a string.
For one dimensional arrays you may use $watchCollection
$scope.names = ['igor', 'matias', 'misko', 'james'];
$scope.dataCount = 4;
$scope.$watchCollection('names', function(newNames, oldNames) {
$scope.dataCount = newNames.length;
});
I'm trying to implement an animdated version of ng-show and ng-hide; I originally tried to use jQueryUI.toggle('slide', …), but since $watch fires multiple times, my elements were toggling in and then immediately toggling out (sometimes more than once). But I saw in AngularJS's github issues that that is $watch's intended behaviour (dirty checking).
So I thought, okay this little eff-you be damned, I'll explicitly show or hide instead of the simple toggle: I broke it down to check the value of $watch's newValue like so:
scope.$watch(condition, function myShowHideAction(newValue,oldValue) {
if ( newValue !== oldValue ) {
if (newValue) {
elm.show("slide", { direction: direction }, "slow");
} else {
elm.hide("slide", { direction: direction }, "slow");
}
}
}
Fire it up, and what do I see? Toggling!
After putting in a few console logs, I discover somehow my condition's value is being changed during $watch's loop (which iterates like 6 times instead of just once, but that should be neither here nor there). However, the actual scope parameter's value does not change (as it shouldn't) half way thru like newValue's does.
What the heck is going on?
Plunker
The reason this happens is because all your infoboxes share the same scope. If you put a console.log(scope) statement in your mySlide directives linking function, you'll see that it's created several times with the same scope. So you have multiple watches for the same condition in the same scope.
Your code isn't the easiest to follow I'm afraid, but the issue seems to be inside my-map.js on the line 87. Instead of doing $compile(elm.contents())(scope);, you should probably do $compile(elm.contents())(scope.$new()); to create an isolated scope for that info box.