Replacing object in array - is it safe for Angular bindings - angularjs

I'm using Angular JS and I've implemented my form with a reset function.
setPrimarySelectionEditEntry is called when I start editing the item on the form. primarySelectionEntry keeps the object that is bound to controls
savePrimarySelectionEdit is called when user hits Save. What I do is I replace item in primarySelection array, as I use this array to display a list
cancelPrimarySelectionEdit does nothing except setting the form to pristine state. Underlying array remains intact
$scope.cancelPrimarySelectionEdit = function () {
$scope.primarySelectionForm.$setPristine();
};
$scope.savePrimarySelectionEdit = function () {
$scope.topicSelection.primarySelection[$scope.formEntryIndex] = $scope.primarySelectionEntry;
$scope.cancelPrimarySelectionEdit();
};
$scope.setPrimarySelectionEditEntry = function (entry) {
$scope.formEntryIndex = $scope.topicSelection.primarySelection.indexOf(entry); ;
$scope.primarySelectionEntry = angular.copy(entry);
};
It works: when I hit save item in the list reflects new values. When I hit cancel, then nothing happens. But I wonder if this replacing array item is safe in the context of angular?

As long as you change the model within Angular's execution context it'll be safe to replace some elements or even the whole array. It won't break neither the data binding nor the event loop.
You only have to worry about changes done outside Angular's loop. For example, a DOM event listener in a directive. Let's say you have the following directive:
angular.module('myApp').directive('resetForm', function(){
return {
link: function($scope, $iElement){
//Add a listener to the element
$iElement.on('click', function(){
//Carefull: changes in scope in this context won't trigger an angular event loop.
$scope.cancelPrimarySelectionEdit();
//So, call $digest to process all the watchers
$scope.$digest();
return;
});
return;
}
}
});
Here, the $scope.cancelPrimarySelectionEdit() function can be called at any time, so a call to $scope.$apply() or $scope.$digest() should be added to tell Angular that something in the model has changed and needs to execute all the watchers from the current scope and its children.

Related

mdbottomsheet disable drag down to close

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.

How to display the value of other controllers in one view dynamically

I am working on an application were index page and inside that I am showing multiple views, In index nav bar I have to show notification count and User name which comes from 2 different controllers,
I am able to display the notification count and User name successfully, but the issue is the values are not changing dynamically.
We need to refresh the page for the new values.
What can I do in this situation can any one please guide me.
I'm guessing you're watching the value directly and not by some object wrapper. In this case javascript isn't actually updating the variable, but assigning a complete new one. Anything out of the function scope that updates the variable will never receive the new value.
The solution is simple; wrap the value in an object and share/inject that object.
angular.module('myApp')
.value('myPageState', {
notificationCount: 0
});
angular.module('myApp')
.controller('myController', function($scope, myPageState) {
$scope.myPageState = myPageState;
});
<div class="my-notification-thingy"> {{ myPageState.notificationCount }} </div>
You can achieve it by:
Maintain those values in rootScope so that you
will have the two way binding.
Making use of emit to notify the parent controller about value changes. This will work only if those two controllers are present in child elements.
In child controllers fire event on value update:
$scope.$emit('valueChanged', {value: val});
In parent controller receive event value:
$scope.$on('valueChanged', function(event, args) {
console.log(args.value);
});

scope.$watch only checks for new value for a single time

I have a function inside a directive (in link attribite):
scope.$watch('pickerSettings', function(pickerSettings){
scope.pickerSettings = pickerSettings;
});
It watches for changes for pickerSettings in another directive to change the current value. Currect directive is used to call a modal. However - the directive only checks for the changes before the modal is opened. If it was opened - the settings are being saved and closing modal and changing the values in other directive will not change the settings. (By the way - modal opening is handled in another service).
I have added the code to log the object properties when opening the modal
scope.showGallery = function () {
console.log(scope);
MediaService.showImagePicker(scope);
};
It only logs the data when called for the first time.
I'm not quite sure why you're doing this:
scope.$watch('pickerSettings', function(pickerSettings){
scope.pickerSettings = pickerSettings;
});
This is watching the scope variable pickerSettings and on the initial digest and/or whenever angular detects a change it is re-assigning the detected change back to the same scope variable. If the scope variable itself is never reassigned to a different value, this watch will never fire again (and thus fire only once).
I'm guessing you probably want to react on a change on one of the sub properties. This can be done by specifying it in the watch expression like this:
scope.$watch('pickerSettings.mySubProperty', function(newValue){
/* handle magic here */
});
... or by using $watchGroup to watch an array of expressions:
scope.$watchGroup([
'pickerSettings.prop1',
'pickerSettings.prop2'
], function(newValues){
var prop1 = newValues[0], prop2 = newValues[1];
/* handle magic here */
});
... or by using $watchCollection to watch (one level deep) sub-properties:
scope.$watchCollection('pickerSettings', function(newValue){
/* handle magic here */
});
... or by using a custom watch function that is called every digest cycle:
function myCustomWatch() {
// If the return value of this function differs from
// the last digest cycle the watch callback is triggered.
if(scope.pickerSettings.items.length) {
return scope.pickerSettings.items[0].someValue;
}
return -1;
}
scope.$watch(myCustomWatch, function(newValue){
/* handle magic here */
});
Hope this helps.

Watch for dom element in AngularJS

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...

Collection create function firing add event too quickly

(Using Backbone 0.9.10)
I have a list view of sorts where the user can click a button to show a modal view. The list view has a counter that shows the amount of items contained in the list. Clicking a button in the modal view executes create on a collection that is passed into both views. This fires the add event in the list view, which in turn runs this function:
renderCount: function () {
// Container for model count
var $num = this.$el.find('.num');
if ($num.length > 0) {
// 'count' is a value returned from a service that
// always contains the total amount of models in the
// collection. This is necessary as I use a form of
// pagination. It's essentially the same as
// this.collection.length had it returned all models
// at once.
$num.html(this.collection.count);
}
}
However, add seems to be fired immediately (as it should be, according to the docs), before the collection has a chance to update the model count. I looked into sync but it didn't seem to do much of anything.
How can I make sure the collection is updated before calling renderCount?
Here's the list view initialize function, for reference:
initialize: function (options) {
this.collection = options.collection;
this.listenTo(this.collection, 'add remove reset', this.renderCount);
this.render();
}
EDIT:
Turns out I was forgetting to refetch the collection on success in the modal view.
$num.html(this.collection.count);
shoule be:
$num.html(this.collection.size());
Backbone.Collection uses methods imported from underscore, here is list: http://backbonejs.org/#Collection-Underscore-Methods
Turns out I was forgetting to refetch the collection on success in the modal view.

Resources