isolate objects from each other, angularjs - angularjs

In my application, I can view, create and update items on a server.
When my page loads, I get items from the server and puts them in an array, I get the ID and Title for each item.
This array is displayed in a table, and when an title in the table is clicked, I get all attributes for that item with that ID from the server, and displays them for the user.
In my controller I have this:
$scope.currentItem = ({
title: '',
id: '',
description: ''
});
So, when Viewing and item I would set currentItem like this:
// function to get item
$scope.currentItem = ({
title: item.Title,
id: item.Id,
description: item.Description
});
Each item also has actions attached to it, so when I view an Item,
I also get that items related actions, these actions is stored in an array:
// function to get Actions
$scope.actionsArray = Actions;
In my real application I have many more objects, and several arrays, this is just an example.
But when I am done viewing or creating an item, how should I clear these arrays and objects, so that if I choose to
view or create another item, I won't have attributes from the previous item in this one?
Right now I have a function like this:
$scope.clearItems = function() {
$scope.currentItem = ({
title: '',
id: '',
description: ''
});
$scope.actionsArray = [];
};
I call this function after create, update, and viewing of an item.
But I still feel as if these different items are not isolated from each other, like in some rare case scenario that their attributes can get mixed up, if I choose to update, create and read a lot of items without refreshing the page, which is what I want to achieve.
So my question in short, how can I achieve 'object isolation' in angular?

You can isolate each item in a directive and then isolate the directive scope.
It's pretty easy to do it in angular.
You have an array of item objects in your main controller :
$scope.items = [{id:1,title:'title',description:'desc'},...];
$scope.aFunction = function(a,b){return a+b};
Main HTML file
<my-item data="item" func='aFunction' ng-repeat="item in items"></my-item>
Then create a directive
app.directive('myItem',function(){
return{
restrict:'E',
scope:{ //This is what makes your directive scope isolated
data:'=', //Think of each attribute as a pipe for data
func:'&'
},
template:'<p>item {{data.id}} : {{data.title}} -> {{data.description}}</p>',
controller:function($scope){
// here you can you $scope.data
// that contain a reference to your item
// and the scope is isolated from the others
// so there is no mixup concerns
// you can also use $scope.func as a function
// in your private scope
}
};
});
you can now manipule each item separately and also manage them all together by doing manipulation on the $scope.items variable in the main controller.
EDIT :
This is important to know : When you're using $scope.datain your isolated scope each attribute of data is like a direct reference to the item attributes.
Within your isolated scope you MUST only modify $scope.data attribute and never directly alter $scope.data or you will create a local and isolated copy of your item and will break the reference.
For example, you want to pass a boolean to your isolated scope then modify it from the isolated scope :
This is in your main controller
$scope.boolean = true;
$scope.anotherBoolean = {value:true};
this is your HTML file
<my-item bool="boolean" anotherbool="anotherBoolean"></my-item>
And in your directive
app.directive('myItem',function(){
return{
restrict:'E',
scope:{
bool:'=',
anotherbool:'='
},
controller:function($scope){
//this will break the reference and create a local isolated copy of $scope.bool
$scope.bool = false;
//this will not
$scope.anotherbool.value = false
}
};
});

Related

Updating an array in the controller scope from a partial

I am using an isteven-multi-select with a controller (ListController) that is using "$scope.mainCategories" for content that is populated by the "ticked" boolean value.
In the header of the application, I am using a select element to allow the user to select a single category (and then be forwarded to the list page). I am using this select element to toggle the ticked boolean value in $scope.mainCategories.
Both are using the same controller, although references separately through UI-Router (possible issue)
views: {
'header#index': {
templateUrl: 'header.html',
controller: "ListController"
},
'container#index': {
templateUrl: 'search.html',
controller: 'ListController'
},
}
then the isteven-multiselect and the select element are in the same partial - the functionality works - when on separate partials the functionality is broken.
Plunker
x might not be what you expect because you can't look for index of object in this line unless you're passing in the actual object:
var x = $scope.mainCategories.indexOf(item);
I assume you're trying to pass in something like:
{
category: "Adventure",
ticked: false
}
and to get the index, it won't work. You need to loop over the array and match the category, for example.
Your approach for modifying the outside array is fine, though.
See this example to see what I mean:
var people = [
{name: 'Shomz'},
{name: 'John'}
];
alert(people.indexOf({ name: 'John'})); // -1: the copy of object not found
alert(people.indexOf(people[1])); // 1: actual reference found
Scope update
To manually update the scope, either wrap the code in a $timeout callback, or use:
$scope.$apply();
$scope.update = function(item) {
item.ticked = true; // ?
};
$scope.mainCategories = [{
category: "Adventure",
ticked: false
},{category: "Fantasy",
ticked: false}];
Just have your function take the object you want to modify and pass that in from the view.

Reference new scope created by adding an object to ng-repeat

I have a set of items in a model tree, which are shown via ng-repeat. Each item in ng-repeat has its own controller (which lets each item have its own properties). Each item has a selected property that is only important per-session, so I'm not saving it as an attribute on the model tree, which is synced to a server.
function ItemCtrl($scope) {
$scope.selected=false;
$scope.Select = function () {
$scope.selected = true;
};
};
Now, when I create a new item by adding it to the model tree, I want to access its scope in the entry created automatically by ng-repeat in order to flip its "selected" variable to true, but I don't know how to access that. I've made a quick fiddle to illustrate my problem. Thoughts?
You can do it with the $rootScope.
Have updated the fiddle link.
Add $rootScope to the ItemCtrl and the Select function.
function ItemCtrl($scope,$rootScope) {
$scope.selected=false;
$rootScope.Select = function () {
$scope.selected = true;
alert("selected is set to true");
};
};
updated link to the fiddle

Predefined function to use in ng-repeat nested in a directive

I have a list directive that basically get all the items from a service and show them in a table. When I click on an item in the table, it opens a form in relation with the type of item.
Everything works great, but now, I have one instance of that list where I need to override the event when I click on an item. So I added an attribute in the directive:
<list factory-name="Workers"
on-item-click="$state.go('worker.workerDetails', {workerId: item._id})">
</list>
So when I get to the function that gets called when I click an item, I can do something like that:
<tr ng-repeat="item in items" ng-click="edit(item)></tr>
$scope.edit = function(item) {
if ($attrs.onItemClick) {
setTimeout(function(){ $scope.$apply($attrs.onItemClick); });
} else {
edit(item);
}
};
The problem is that I cannot isolate the scope since some nested directive need to access it and I would prefer not to modify my list directive with a bunch of exception, only an override function (onItemClick);
Right now it's not working, the stateparams don't get assigned.
Thank you very much
In case you can't isolate scope and use "&" bingind, you should use $parser service.
First parse attribute:
var onItemClick = $parse($attrs.onItemClick);
then call it like this:
onItemClick($scope, { item: item });
You pass your context scope as the first argument, and an object containing local (per call) variables the second argument.
And not to polute outer scope, you can use "child scope", by specifying scope: true in directive definition.

$locationChangeSuccess triggers four times

I am new to angular Js.
My application flow is as below:
1) I have a view controller wherein, each view controller sets the breadcrumb data with the help of Breadcrumbs factory.
2) Breadcrumbs factory takes data from view controller and attaches data to $location.$$state object.(reason for storing in state object is if back button is pressed, view controller doesn't instantiate so I can refer history data for breadcrumbs ) below is code to attach data to state object:
var state = $location.state();
state.breadcrumb = breadcrumbData;
$location.replace().state(state);
3) I have also created breadcrumb directive on global header which will display breadcrumbs on $locationChangeSuccess event. Directive will take data from $location.state(); which was set in factory.
My problem is when location is changed, $locationChangeSuccess event callback function executes four times.
below is my directive code:
angular.module('cw-ui')
.directive('cwBreadcrumbs', function($location, Breadcrumbs, $rootScope) {
return {
restrict: 'E',
replace: true,
templateUrl: 'UI/Directives/breadcrumb',
link: function($scope, element){
//some code for element...
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
// get data from history of location state
var data = $location.state();
console.log(data);
});
}
};
});
output is as below:
Object {}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0], breadcrumb: Array[2]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
breadcrumb: Array[2] disappears 1st, 3rd and 4th times. I really don't know what is causing this callback function execute four times, and I have no clue about an issue and don't know how to debug. Please help guys!
After running into this myself, the problem lies in the fact you are using the root scope to bind the locationChangeSuccess event from within a directive that is either encountered multiple times on a single page, or encountered multiple times as you revisit the page:
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
Since you are binding to the rootScope, and the rootScope does not go out of scope, the event binding is not cleaned up for you.
Inside your link function, you should add a listener for the element $destroy, as well as capture the return value from the original bind, so you can later unbind it.
First: capture return value:
var unbindChangeSuccess = $rootScope.$on('$locationChangeSuccess' ...
Next, unbind that value in your destroy method:
element.on('$destroy', function() {
unbindChangeSuccess();
});
That should solve the multiple calls to your locationChangeSuccess! :)

Select New DOM elements created by Angularjs ng-repeat

I am using Angularjs for a web application. I have tried searching to find a solution to my problem, and it seems that Angularjs do not facilitate an easy way to access newly created DOM elements within ng-Repeat.
I have prepared a jsfiddle to show the actual problem.
here is the link: http://jsfiddle.net/ADukg/956/
Please let me know how to select the new DOM element within ng-repeat.
To expand on my comment, I have updated your fiddle to show a simple implentation of a directive that alerts the class of the element.
http://jsfiddle.net/ADukg/994/
Original comment:
Regarding dom manipulation in the controller, it says here that you should not do it at all. It should go in a directive. The controller should only contain business logic.
Why it doesn't work, I don't know, but it's probably because angular is still running its own stuff at this point in time.
There are two ways to do this:
1 ng-init
function controller($scope) {
$scope.items = [{id: 1, name: 'one'}, {id: 2, name: 'two'}];
$scope.initRepeaterItem(index, item) {
console.log('new repeater item at index '+index+':', item);
}
}
<ul>
<li ng-repeat="item in items" ng-init="initRepeaterItem($index, item)"></li>
</ul>
2 MutationObserver slightly more complex
Do this inside a directive, on the element whose parent gets new children (<ul> in this case)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// you get notified about DOM mutations here
// check mutation.type if it was an insertion
console.log(mutation.target.nodeName, mutation.type, mutation);
});
});
var config = {childList: true, attributes: false, characterData: false,
subtree: false, attributeOldValue: false, characterDataOldValue: false};
observer.observe(element[0], config);
Demo http://plnkr.co/h6kTtq
Documentation https://developer.mozilla.org/en/docs/Web/API/MutationObserver
Browser support http://caniuse.com/mutationobserver

Resources