Updating an array in the controller scope from a partial - angularjs

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.

Related

Passing a variable from a controller into a route param

....
.state('books', {
url: '/book/chapter/:chap',
templateUrl: "views/chapter.html",
params: { chap: .........}
})
Hi!
I have a variable in a specific controller and I want to pass it's value into a param for routing. Eventually I want to change the value of that variable so that I can create new urls using this same route.
I'm using ui-router, as you may have noticed.
I'm also curious on how would you solve the following problem:
basically I want to open a specific chapter of a book, let's say chapter 5. Then I want to display at the left of the page a link for each remaining chapter, that's why I want to change the value of the variable. How would you solve this using ng-repeat?
I'm thinking of using getArticle (as shown below) to get a chapter number and then ng-repeat the remaining chapters with ng-repeat? Ideas?
angular
.module('BookLibrary')
.controller("ChapterController", ["$scope","stateParams", "ChapterControllerService", function($scope, $stateParams, ChapterControllerService){
$scope.chapterList = ChapterControllerService.chapters;
$scope.getArticle = chapterList[0].chapter;
}]);
chapterList looks like this:
chapterList = [
{
chapter: "1",
content: "Chapter 1 goes here"
},
{
chapter: "2",
content: "Chapter 2 goes here"
},
{
chapter: "3",
content: "Chapter 3 goes here"
}
];
There are multiple questions here so to start with your state parameters. In the state params you can declare default values for params in a state like so.
.state('books', {
url: '/book/chapter/:chap',
templateUrl: "views/chapter.html",
params: { chap: null}
})
This would default the value to null. Perhaps you always want to default to chapter 1? If that was the case you can use params: {chap: '1'} (since you are using strings).
To pass a value to the state there are multiple ways to do it. Assuming you are using ui-sref you can hand parameter values right in the link.
ui-sref="books({ chap: 3 })"
However you want to have a list of links from an object using ng-repeat. So you could do something like this
<ul>
<li ng-repeat="chapter in chapterList">
<a ui-sref="books({chap: chapter.chapter})">{{chapter.chapter}}</a>
</li>
</ul>
You've included $stateParams in your controller already so you just need to grab the parameter from it like so
angular
.module('BookLibrary')
.controller("ChapterController", ["$scope","$stateParams",
"ChapterControllerService", function($scope, $stateParams,
ChapterControllerService){
$scope.currentChapter = $stateParams.chap;
$scope.chapterList = ChapterControllerService.chapters;
$scope.getArticle = chapterList[0].chapter;
}]);

Passing Array of Objects into Angular Directive

I have an array of objects in my angular controller, e.g.
[{
label: 'label1',
data: [{
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 1])
}, {
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 2, 1])
}]
}, {
label: 'label2',
data: [{
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 11])
}, {
type: TimelineChart.TYPE.POINT,
at: new Date([2015, 1, 15])
}]
}];
Every object has a Date field. How can I pass this array of objects into my directive?
I tried passing by reference:
<div id= "chart" my-directive my-data="data"></div>
$scope: {
myData: '='
},
link: function($scope, $element, $attrs) {
function init() {
var data = $scope.myData;
var timeline = new TimelineChart($element[0], data);
}
$attrs.$observe("myData", init);
}
However, it didn't work. I get undefined.
Passing in as an attribute, i.e. my-data="{{ data }}" and using JSON.parse($attrs.myData) does not work due to the Date fields.
It's very simple to pass the data to directive. I think what you are missing is scope. You are using $scope instead of scope. Directive Definition object takes scope not $scope. So what you need to do it.
scope: { //make it scope
myData: '='
},
link: function($scope, $element, $attrs) {
function init() {
var data = $scope.myData;
var timeline = new TimelineChart($element[0], data);
}
$attrs.$observe("myData", init);
}
Here's the fiddle
I think the problem is your array object is lost in $scope . For confirmation, use console.log($scope.myData);
And see in browser console what it returns, if undefined then simply write console.log($scope); and then run the application . Go to browser console and expand $parent until you find myData object. Count the number of parent scope.
If suppose you found it under 3rd parent ,the use
$scope.$parent.$parent.$parent.myData instead of $scope.myData .
This will work :)

angular array item not updating on scope change

Spent a few hours on this already, sifted through numerous stack posts and blogs but can't seem to get this to make my model update. More specifically, I am trying to update an array item (ng-repeat). In the simple case below, I iterate over venues list, and upon toggling a "like" button, I update the server appropriately, and reflect the change on the venues item on the $scope.
in my search.html I have a directive:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
and search controller I have:
app.controller('bleh', function(Service) {
...
$scope.venues = [{ id: 1, name: 'Venue1', like: false },{ id: 2, name: 'Venue2', like: false }];
...
});
Nothing unusual there. Then in my venues-list directive:
app.directive('venues-list', function() {
function venueListController($scope, Service) {
$scope.likeToggle = function(venue, $index) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venues[$index].like= !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueListController,
scope: {
venues: '='
}
}
});
then in my venue.html I have:
<div ng-repeat="venue in venues">
<p>{{venue.name}}</p>
<button ng-click="likeToggle(venue, $index)">Like</button>
</div>
I have tried many different options:
$scope.$apply() // after updating the $scope or updating scope within apply's callback function;
$scope.$digest()
$timeout(function() { // $scope.venues[$index] .... }, 0);
safe(s,f){(s.$$phase||s.$root.$$phase)?f():s.$apply(f);}
so
safe($scope, function() { $scope.venues[$index].like = !venue.like });
I haven't yet used the link within the directive, but my venues.html template is obviously a little more elaborate than presented here.
EDIT:
Just to keep the discussion relevant, perhaps I should have mentioned - the data is coming back from the server with no issues, I am handling errors and I am definitely hitting the portion of the code where the $scope is to be updated. NOTE: the above code is a small representation of the full app, but all the fundamental pieces are articulated above.
Search Controller
Venues Service
venue-list directive and venue.html template to accompany the directive
directive controller
EDIT #2
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
Just to keep it even simpler, the above doesn't work - so really, the bottom line is my items within an array are not reflecting the updates ...
EDIT #3
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
My apologies - just to re-iterate what I was trying to say above - the above is changing the scope, however, the changes are not being reflected on the view.
Perhaps the issue is with your service and promise resolution.. Can you put a console.log there and see if the promise resolution is working fine? or Can you share that code bit. Also where are you checking for scope update? within directive or outside
OK after some refactoring I finally got it working.
The "fix" (if you want to call it that) to my specific problem was:
instead of passing an array of venues, I was iterating over the array on the parent controller, passing in a venue as an element attribute that would bind (two-way) on the isolated scope of the directive.
so, instead of:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
I now have:
<ion-content>
<venues-list ng-repeat="venue in venues" venue="venue"></venues-list>
</ion-content>
and my directive now looks like:
app.directive('venues-list', function() {
function venueController($scope, Service) {
$scope.likeToggle = function(venue) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venue.like = !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueController,
scope: {
venue: '='
}
}
});
This did the trick!

Angular 1.5 component attribute presence

I'm refactoring some angular directives to angular 1.5-style components.
Some of my directives have behavior that depends on a certain attribute being present, so without the attribute having a specific boolean value. With my directives, I accomplish this using the link function:
link: function(scope,elem,attrs, controller){
controller.sortable = attrs.hasOwnProperty('sortable');
},
How would I do this with the angular 1.5-style component syntax?
One thing I could do is add a binding, but then I'd need to specify the boolean value. I'd like to keep my templates as-is.
Use bindings instead of the direct reference to the DOM attribute:
angular.module('example').component('exampleComponent', {
bindings: {
sortable: '<'
},
controller: function() {
var vm = this;
var isSortable = vm.sortable;
},
templateUrl: 'your-template.html'
});
Template:
<example-component sortable="true"></example-component>
Using a one-way-binding (indicated by the '<') the value of the variable 'sortable' on the controller instance (named vm for view model here) will be a boolean true if set as shown in the example. If your sortable attribute currently contains a string in your template an '#' binding may be a suitable choice as well. The value of vm.sortable would be a string (or undefined if the attribute is not defined on the component markup) in that case as well.
Checking for the mere presence of the sortable attribute works like this:
bindings: { sortable: '#' }
// within the controller:
var isSortable = vm.sortable !== undefined;
Using bindings may work but not if you are trying to check for the existence of an attribute without value. If you don't care about the value you can just check for it's existence injecting the $element on the controller.
angular
.module('yourModule')
.component('yourComponent', {
templateUrl: 'your-component.component.html',
controller: yourComponentsController
});
function yourComponentController($element) {
var sortable = $element[0].hasAttribute("sortable");
}
There is a built-in way to do this by injecting $attrs into the controller.
JS
function MyComponentController($attrs) {
this.$onInit = function $onInit() {
this.sortable = !!$attrs.$attr.hasOwnProperty("sortable");
}
}
angular
.module("myApp", [])
.component("myComponent", {
controller: [
"$attrs",
MyComponentController
],
template: "Sortable is {{ ::$ctrl.sortable }}"
});
HTML
<my-component sortable>
</my-component>
<my-component>
</my-component>
Example
JSFiddle

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
}
};
});

Resources