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

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

Related

ng-repeat only updating after clicking another tab or typing in a textbox

I'm pretty new to angular and have been having a bit of a problem in trying to create a basic "to-do" list sort of app.
There are various categories in the sidebar, and the user can click a button that brings up a modal prompting the user for the name of a new category. This name is used to create a new category, which is pushed onto the preexisting array.
However, the new category is only appearing after I start typing in another text-box on the screen or click on another tab.
The code that should be relevant:
var list = this;
$(document).on("click", ".prompt", function(e) {
bootbox.prompt("What do you want your new category to be?", function(result) {
if(result !== null) {
list.addCategory(result);
}
});
});
this.addCategory = function(result) {
if(result.trim() != "") {
var newCategory = new Category(result);
list.categories.push(newCategory);
this.setCategory(newCategory);
}
};
I can't seem to figure out how to post HTML as a code block, but here's the directives used to list out the categories (with categoryCtrl being the controller in question): ng-class="{active: categoryCtrl.isSet(category) }" ng-repeat="category in categoryCtrl.categories" ng-init="categoryCtrl.currCategory = categoryCtrl.categories[0]"
I know that the array is being updated immediately - if I add an alert 'bootbox.alert(list.categories[list.categories.length-1].name)' the alert gives me whatever the input was like it's supposed to. It's just not showing up in the ng-repeat.
Another interesting observations is that the ng-init overrides the this.setCategory(newCategory) - so it seems that when the list does update, it is reverting to its ng-init value.
Other places where I have an ng-repeat of an array, it's updated automatically when something new is pushed onto it. I'm wondering if it may have something to do with the modal/usage of bootbox - everywhere else things are added either by a checkbox or keying something into a textbox on screen, this is the only place where a modal is used.
Here is a working plunker based on your code.
The app looks like below. I initialize an array with dummy data for the example, but an empty array would work too. I used the vm = this syntax similar to what you have. When calling $nbBootbox.prompt it returns a promise so you need to use the then() syntax like below:
var app = angular.module('plunker', ['ngBootbox']);
app.controller('MainCtrl', ['$scope', '$ngBootbox', function($scope, $ngBootbox) {
var vm = this;
vm.name = 'World';
vm.categories = ['Category 1', 'Category 2'];
vm.prompt = function() {
$ngBootbox.prompt('Enter a new category?')
.then(function(result) {
console.log('Prompt returned: ' + result);
vm.categories.push(result);
}, function() {
console.log('Prompt dismissed!');
});
}
}]);
To make your HTML more angular like I changed it to this and also use the ControllerAs syntax:
<body ng-controller="MainCtrl as vm">
<p>Hello {{vm.name}} !</p>
<ul>
<li ng-repeat="c in vm.categories">{{c}}</li>
</ul>
Add Category
</body>
So, the link calls the prompt() function... it opens the modal and if you enter in the category, I push it to the categories array and it is added automatically to the page as a new bullet point in the list of categories.
From the documentation:
$ngBootbox.prompt(msg)
Returns a promise that is resolved when submitted and rejected if dismissed.
Example
$ngBootbox.prompt('Enter something')
.then(function(result) {
console.log('Prompt returned: ' + result);
}, function() {
console.log('Prompt dismissed!');
});
Hope this helps. let us know.

isolate objects from each other, 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
}
};
});

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.

Update directive variable from controller

New to AngularJS I have a simple directive, service and controller. I loop through a list of items from a database in the controller embedded in the directive to render a checkbox list of items. From a form I am able to update my list of items in the database. I would at the same time like to update my list of displayed items with the newly added item and was hoping to benefit from Angulars two-way binding but I can't figure out how...
My directive:
angular.module('myModule').directive('menu', function (menuService) {
return {
restrict: 'E',
templateUrl: '../templates/menu.html',
controller: function () {
var me = this;
menuService.getMenuItems().then(function(data) {
me.items = data;
});
},
controllerAs: 'menu'
};
});
and corresponding html:
<div ng-repeat="item in menu.items">
<div class="col-md-4" ng-if="item.menuItem">
<div class="checkbox">
<label for="{{item._id}}">
<input id="{{item._id}}" type="checkbox" name="menuItems" ng-model="menuItems[$index]" ng-true-value="{{item._id}}">
{{item.menuItem}}
</label>
</div>
</div>
</div>
My issue is now that if I add a new item using this controller
EDIT: On my page I have two things. 1: A list of items rendered using the directive above and 2: A simple form with a single input field. I enter a new menuItem and click "Save". That triggers the controller below to be called with the new menuItem. The menuItem is then added to a collection in my database but I would like the list on my page to update with the newly added item. Preferably without having to reload the entire page.
$scope.insertMenuItem = function () {
$http.post('/menuItems', $scope.formData)
.success(function (data) {
$scope.items = data;
});
};
then the "me.items" in my directive remains unchanged hence so does my list in my browser.
How do I "tie it all together" so that when I call insertMenuItem then my.items are updated automagically?
This didn't come out as well as I had hoped but I hope you get the meaning...
Thanks in advance
take a look at this http://jsbin.com/rozexebi/1/edit, it shows how to bind a list of items in a directive to a controller, hope it helps.

AngularJS : ng-repeat list not updating - directives

I am new to angular and I have the following code:
Here is my html:
<drawer title="'Add a new Item'"
visible="showAddItemDrawer"
on-close="showAddApplicationItem = false">
<!-- items list refresh does not work from inside the drawer. -->
<add-item></add-item>
</drawer>
<!--list refresh works if i place the code out here.
<add-item></add-item> -->
<div ng-repeat="item in itemList">
<item-list></item-list>
</div>
Here is my directive.js:
directive("additem", function () {
return {
restrict:"E",
transclude:true,
controller:"ItemsCtrl",
templateUrl:"partials/add-item.html" //contains a form to add item
};
})
.directive("itemlist", function () {
return {
restrict:"E",
transclude:true,
templateUrl:"partials/item-list.html" //contains code to display the items in a list
};
})
I have a form in add-item.html to add an item. The form shows up when you click on add button(like an accordion). I call the push() to add a new item to the scope.
The list update works if i place the directive call outside the drawer..
If i place it inside the drawer,the scope is not getting updated until i hit refresh.
Can anyone point me what I am doing wrong with the directives? Thanks a lot!
Edit: Added additional code :
In the form to add an item:
<button type="submit"
ng-click="addItemService()">
Add Item
</button>
addItemService() code:
$scope.addItemService = function () {
var data = {
"name": $scope.itemName,
};
ItemService.addItem(data, $scope.listgroupid)
.success(function (result) {
$scope.itemName = "";
viewList(); //The function that sets the scope of the list
})
.error(function () {
});
};
viewlist() code:
var viewList = function () {
ListService.getList($scope.listgroupid)
.success(function (result) {
$scope.itemList = result;
//In the angular inspector, I am able to see the new item in the variable result
})
.error(function () {
});
};
EDIT: function scope is different from variable scope, use $scope.$parent.itemList when referencing new variable value
i had an issue where the directive scope (while debugging) and the batarang inspected scope (when selecting an element in the dev tools and typing $scope.varname in the console) were not matching after a successfully resolved promise.
the promise updates a list for a ng-repeat which contains a directive.
the debugged scope held the values from the last data before the promise was loading new data.
so i added a timeout around the data-loaded event broadcast which pushes the broadcast into the next digest cycle and angular gets informed about the update.
$scope.$apply most often throws the "apply already in progress" error.
the timeout solves this a little more softly...
$timeout( function () {
// broadcast down to all directive children
$scope.$broadcast('dataLoaded');
}, 1);
need to see the code wherein you are calling push for item.
In the absence of that I suspect this is scope issue. Remember that ngRepeat and drawer reside in difference scopes by default and until you have explicitly made them share certain variables , changes inside drawer won't be reflected in ngRepeat. For more on scopes inside directives read http://docs.angularjs.org/api/ng.$compile.
The 'isolate' scope takes an object hash which defines a set of local
scope properties derived from the parent scope. These local properties
are useful for aliasing values for templates. Locals definition is a
hash of local scope property to its source:
# or #attr - bind a local scope property to the value of DOM
attribute. The result is always a string since DOM attributes are
strings. If no attr name is specified then the attribute name is
assumed to be the same as the local name. Given and widget definition of scope: { localName:'#myAttr' },
then widget scope property localName will reflect the interpolated
value of hello {{name}}. As the name attribute changes so will the
localName property on the widget scope. The name is read from the
parent scope (not component scope).
= or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value
of the attr attribute. If no attr name is specified then the attribute
name is assumed to be the same as the local name. Given and widget definition of scope: {
localModel:'=myAttr' }, then widget scope property localModel will
reflect the value of parentModel on the parent scope. Any changes to
parentModel will be reflected in localModel and any changes in
localModel will reflect in parentModel. If the parent scope property
doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION
exception. You can avoid this behavior using =? or =?attr in order to
flag the property as optional.
& or &attr - provides a way to execute an expression in the context of
the parent scope. If no attr name is specified then the attribute name
is assumed to be the same as the local name. Given and widget definition of scope: {
localFn:'&myAttr' }, then isolate scope property localFn will point to
a function wrapper for the count = count + value expression. Often
it's desirable to pass data from the isolated scope via an expression
and to the parent scope, this can be done by passing a map of local
variable names and values into the expression wrapper fn. For example,
if the expression is increment(amount) then we can specify the amount
value by calling the localFn as localFn({amount: 22}).
You can either use a $rootScope.$broadcast() to fire an event that your directive would capture, and then let the directive update the list with ng-repeat or you could call a $scope.$apply() with the updation wrapped in it within the controller to indicate angular that he list is updated, but when using $scope.$apply() be careful, and make sure it that it is delayed/deferred until the next digest cycle.
A library called Underscore.js lets you delay $apply until the next digest cycle.

Resources