AngularJS update model on click of element and also toggle CSS class - javascript

New to Angular and a bit confused. I have a list item that needs to display a tick or a cross depending on an initial value from its controller.
When a user clicks the list item I want to change the value to its current opposite and then update the CSS class to reflect this in the DOM.
Currently I have the following controller:
app.controller('SetupSettingsCtrl', ['$scope', '$rootScope', '$location', function ($scope, $rootScope, $location) {
console.log('setup controller loaded');
$scope.data ={
about: {
uie: '439213949123I034',
appVersion: '3.23453'
},
lab: {
sleep: false,
move: true
},
stats: {
optOut: true
}
};
$scope.chkItem = function($event, prop){
console.log(prop);
};
}]);
And the following template partial:
<div class="pure-u-1">
<h1 class="h2 text-center">About</h1>
<p class="text-center">Phone UIE: <span class="text-valid">{{uie}}</span></p>
<p class="text-center">App version: <span class="text-valid">{{appV}}</span></p>
<p class="text-center"><i class="icon-refresh"></i> Manual Update</p>
</div>
<div class="pure-u-1">
<h2 class="text-center">LAB functions</h2>
<section class="view-content">
<ul class="center-block list-bare list-icon-box-chk">
<li class="pointer" ng-class="{'un-chk': !sleep}" ng-model="sleep" ng-click="chkItem($event)">Sleep with phone on bed</li>
<li class="pointer" ng-class="{'un-chk': !move}" ng-model="move" ng-click="chkItem($event)">Movement checker</li>
</ul>
</section>
</div>
<div class="pure-u-1">
<h2 class="text-center">Anonymous Statistics</h2>
<section class="view-content">
<ul class="center-block list-bare list-icon-box-chk">
<li class="pointer" ng-class="{'un-chk': !optOut}" ng-model="optOut" ng-click="chkItem($event)">I do not want anonymous statistics to be geathered for Health research, and healthcare improvement</li>
</ul>
</section>
</div>
I do not now how to pass the model reference to update the $scope value to trigger the change? When I pass the model property reference I get the value.
I need to call the controller method to pass the model value to the server also.

You should do both: toggle the model as well as call the function inside ng-click without the need of passing the model as a parameter. Also you dont need to bind the model to the lis:
<li class="pointer"
ng-class="{'un-chk': !data.lab.sleep, 'chk': data.lab.sleep}"
ng-click="data.lab.sleep = !data.lab.sleep; chkItem($event)">
Sleep with phone on bed
</li>
<li class="pointer"
ng-class="{'un-chk': !data.lab.move, 'chk': data.lab.move}"
ng-click="data.lab.move = !data.lab.move; chkItem($event)">
Movement checker
</li>
(I guess those {{uie}} and {{appV}} in your HTML need to be like {{data.about.uie}} and {{data.about.appVersion}})
JS:
$scope.chkItem = function($event){
/* do something here */
};

In your "Controller" it will be avaliable
$scope.chkItem = function($event, prop){
console.log(data.lab.sleep);
};
You can update directly no need to pass
<li class="pointer" ng-class="{'un-chk': !data.lab.sleep}" ng-model="data.lab.sleep" ng-click="chkItem($event)">Sleep with phone on bed</li>

Related

Angularjs nested controller called just one time

I'm new in Angularjs and I have an app, with some "projects", which have a local menu displayed on some pages. Index.html contains the main navbar with footer :
<body ng-app="ysi-app" ng-controller="MainController">
<div class="page-content">
<div class="row">
<div class="col-md-2">
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<!-- Main menu -->
<li ng-if="isAuthenticated()">{{name}}</li>
<li class="current">Dashboard</li>
<li>Projects</li>
</ul>
</div>
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-include="'views/localMenu.html'" ng-controller="LocalMenuController">
</div>
</div>
<div ng-view></div>
</div>
So I have a nested controller LocalMenuController for the local menu and a main controller. The project controller sets the datas :
angular.module('ProjectCtrl',[]).controller('ProjectController',function($scope,$location, ProjectService,$route, AuthenticationService, $rootScope){
$scope.setProjectDatas = function(projectName, projectId){
ProjectService.setName(projectName);
$rootScope.projectId = projectId;
};});
I set the id of one project to the $rootScope for testing (I have a Service which will do that better) and get it in the LocalMenuController :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
$scope.projectId = '';
$scope.projectId = $rootScope.projectId;
});
I display projects in a table and when I clicked on one of it, the function setProjectDatas(name,id) is called. The problem is when I clicked on one project, the id of the project is correct and set but when I go previous and clicked on another project, the id is the old id of the project previously clicked. The datas are not updating. I googled my problem but found nothing on it.
I think the LocalMenuController is called only one time but not after.
What am I doing wrong ?
Thank you
UPDATE
I've created a Directive which displays the template but it's still not updating the partial view localMenu.
LocalMenu Directive :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
console.log('-> LocalMenu controller');
})
.directive('localMenu', function($rootScope){
return {
templateUrl: '/YSI-Dev/public/views/partials/localMenu.html',
link: function(scope){
scope.projectId = $rootScope.projectId;
}
};
});
A part of index.html
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-controller="LocalMenuController">
<div local-menu></div>
</div>
Partial view localMenu :
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<li><i class="glyphicon glyphicon-list-alt"></i> Backlog</li>
<li><i class="glyphicon glyphicon-user"></i> My team </li>
</ul>
</div>
I'm trying to get the projectId from the $rootScope and inject it in the <a href="#/project/{{projectId}}" but I have some troubles. What's the way to do that ?
First of all, try using directives instead of ng-controller. You can encapsulate your code and template into a unit. You can also try creating a component. Pass some data to the directive/component and Angular will take care of updating the template and running whatever needs to run within the directive/component. (Given that you used two-way data-bindings)
From the code above, I cannot see what would trigger LocalMenuController to run again.

Scope variable not updating in view despite $scope.$digest

.controller('PizzaCtrl', ['$scope','$state','$ionicLoading',
function($scope, $state, $ionicLoading) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$scope.items = $scope.$parent.menu.pizze;
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
The data all checks out. The correct item information is logged to the console. However, the ng-repeat="item in items" directive in my view does not update with the pizza items.
I tried using $scope.$apply and $scope.$digest inside the event listener, but the console threw an error saying the digest was already in progress.
Also worth noting that this controller has two sibling controllers that have identical logic to this one, except for different sections of the menu. The console.log("pizza") statement isn't executed until I click into the state.
Is there a clear reason why my view is not updating?
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()">
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>
Instead of using $scope.$apply try to use $timeout angular service.
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
.controller('PizzaCtrl', ['$scope','$state','$ionicLoading', '$timeout'
function($scope, $state, $ionicLoading, $timeout) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$timeout(function(){
$scope.items = $scope.$parent.menu.pizze;
});
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
Turns out the solution to this problem is that I needed to add a missing closing tag to the <ion-refresher> tag.
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()"></ion-refresher>
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>

ng-click not firing $scope.doSomething(), onclick for same function works

The controller I'm using is:
angular.module('app.PostView', [])
.controller('PostViewCtrl', function($scope, $http, Constants) {
$scope.posts = [];
$scope.doSomething = function(){
console.log("in doSomething");
}
$http.get(Constants.POST_URL)
.then(function (response){
console.log(response);
var post = new PostFactory(response.data);
$scope.posts.push(post);
});
})
The view for the controller is
<div ng-repeat="post in posts" >
<div class="home-container">
<div id="details-container">
<!-- using single item Array instead of single iftem to fix linkify bug -->
<div ng-bind="post.desc"></div>
<span class="item-note single-post-details">
<!-- <div class="time-text">{{post.id}}</div> -->
<div class="title" >{{post.title}}</div>
</span>
<a ng-click="doSomething()" href="#" >{{post.name}}</a>
</div>
</div>
</div>
If I replace ng-click with onclick then doSomething is triggered. Currently it is not. The same Controller and html code in other controller/views does work.
When you clicking on the anchor, it change the routing as http://url/#, that listen by the $routeProvider & ng-view loads the default view and template as you are seeing on your side.
Basically you need to remove href="#" from your anchor link or just put href="" blank in that anchor. That would fix your problem.
<a ng-click="doSomething()" href="" >{{post.name}}</a>
Remove href completely
<a ng-click="doSomething()"
style='cursor:pointer' >{{post.name}}</a>

Collection-Repeat and $ionicModal Strange Behavior

I am making a directory app that comprises roughly 200 list items (employees). The app worked as intended using ng-repeat, however, it was sluggish to load. I switched to Collection-Repeat to take advantage of the speed boost but I am getting bizarre behaviors that I can't figure out.
The list items are rendering correctly, alphabetically with the category titles added successfully. The problem is, each list item has a ng-click attribute that opens an $ionicModal. The modal for each item opens, but the loaded data is incorrect.
When the modal opens, it starts at the bottom of the page - I can see the contents for half a second before it animates to the middle of the screen. To start, the loaded data is correct. As it animates, it switches to another employees data. I can't seem to figure out why. I'm new to angular/ionic so any pointers would be great. Thanks!
EDIT - Out of curiousity, I added a second ng-controller="ModalCtrl" ng-click="openModal();" to each element as a button. Clicking on the element does the usual - opens the modal with the wrong employee. Clicking on the newly created button however creates TWO modals (stacked on eachother) BOTH with the correct employee. Removing either instance to the ng-controller or ng-click puts me back at square one with only one modal of incorrect data. Why is this? Why does adding a second ng-click correct the problem (despite having two modals)?
EDIT - Here is a link to a codepen sample (dumbed down, but proves my issue: http://codepen.io/anon/pen/zijFv?editors=101
My HTML looks like this:
<div class="list">
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}">
<!-- ADDED BUTTON SEE EDIT COMMENT ABOVE -->
<button ng-if="!row.isLetter" ng-controller="ModalCtrl" ng-click="openModal();">Click</button>
<img ng-controller="ModalCtrl" ng-click="modal.show()" ng-if="!row.isLetter" ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
</div>
My Modal HTML is this:
<header class="bar bar-header bar-lsi">
<h1 class="title">Contact Information</h1>
<div class="button button-clear" ng-click="closeModal()">
<span class="icon ion-close"></span>
</div>
</header>
<ion-content has-header="true" style="margin-top: 0px !important;">
<div class="list card" style="border-radius: 0px !important;">
<div class="item item-avatar item-text-wrap">
<img ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.firstname}} {{row.lastname}}</h2>
<p>{{row.jobtitle}}</p>
</div>
<a href="tel:{{row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{row.phone}}
</a>
<a href="mailto:{{row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{row.email}}
</a>
</div>
</ion-content>
And then I have my basic controller:
.controller('ModalCtrl', function($scope, $ionicModal) {
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModal = function() {
$scope.modal.hide();
};
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
})
I think the problem is that you aren't passing to the modal template any value. It's getting residual values. I see too that you are using too much ng-controller and ng-click in items list and what is inside it. I mean, if you use ng-click for A.item, you don't need to use ng-click for the image inside it.
Let's see some code:
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}"
ng-controller="ModalCtrl" ng-click="openModal(row);">
<img ng-if="!row.isLetter" ng-src="http://placehold.it/65x65">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
As you can see, I've removed all ng-click and ng-controller inside A tag, and I've left only what is attributes of A tag. You can notice too that I pass the object row to the openmModal() function.
In controller, I've made next changes:
$scope.openModal = function(item) {
$scope.modal.row = item;
$scope.modal.show();
};
And in the modal template I've used modal.row as variable with the data from the item list touched. So in template I use it like this:
<div class="item item-avatar item-text-wrap">
<img ng-src="http://placehold.it/65x65">
<h2>{{modal.row.firstname}} {{modal.row.lastname}}</h2>
<p>{{modal.row.jobtitle}}</p>
</div>
<a href="tel:{{modal.row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{modal.row.phone}}
</a>
<a href="mailto:{{modal.row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{modal.row.email}}
</a>
I've test it in your codepen and it works. Try it and tell me if it works for you.

Ng-repeat not rendering content

I'm writing a web app using Node.js+Express to serve (with HoganJs as a templating engine) and AngularJS on the frontend. I'm having problems with ng-repeat rendering the correct number of elements, but without any content in. I broke the ng-repeat down into a smaller example and it's still not rendering.
EDIT: There was a typo in the Plunkr, so I removed it. Here's a more expanded extract from my app.
Here's a section of my view: index.hjs
<div class="search-results" ng-controller="results">
<ul class="tracks">
<li class="track" ng-repeat="track in tracks">
<ul class="meta">
<li>
<div class="name">
<span class="value">{{track.name}}</span>
</div>
</li>
<li>
<div class="album">Album:
<span class="value">{{track.album}}</span>
</div>
</li>
<li>
<div class="artist">Artist:
<span class="value">{{track.artist}}</span>
</div>
</li>
<li>
<div class="length">Length:
<span class="value">{{track.length}}</span>
</div>
</li>
</ul>
</li>
</ul>
</div>
The results controller: js/controllers/results.js
var results = function($scope, socket) {
$scope.tracks = [
{"uri":"spotify:track:1jdNcAD8Ir58RlsdGjJJdx","name":"Ho Hey","artist":"The Lumineers","album":"The Lumineers"},
{"uri":"spotify:track:3uuGbRzMsDI5RiKWKOjqWL","name":"Hey Porsche","artist":"Nelly","album":"Hey Porsche"},
{"uri":"spotify:track:5BSndweF91KDqyxANsZcQH","name":"Ho Hey","artist":"The Lumineers","album":"The Lumineers"},
{"uri":"spotify:track:2UNc0duOP4cS7gqYFFkwxT","name":"Hey Girl","artist":"Billy Currington","album":"Hey Girl"},
{"uri":"spotify:track:6fgbQt13JlpN59PytgTMsA","name":"Snow [Hey Oh]","artist":"Red Hot Chili Peppers","album":"Snow [Hey Oh]"}
];
socket.on("results", function(tracks) {
$scope.tracks = tracks;
console.dir(JSON.stringify($scope.tracks));
});
$scope.add = function(uri) {
socket.emit("add", uri);
};
};
And finally my module: app.js
var app = angular.module("app", []);
var factories = {
socket: socket
};
app.factory(factories);
var controllers = {
actions: actions,
search: search,
results: results,
queue: queue
};
app.controller(controllers);
For testing purposes, the tracks are hardcoded in when the app is run 5 lis are rendered but there no content has been templated inside of them.
Remove a ) here:
];
});
^
Plnkr: http://plnkr.co/edit/e0URFt?p=preview
I've just realised what's happening here. Hogan.js is overwriting angular's templates when the page is rendered at the server.
Just remove the extra bracket added to your script (line 19). Please look at the script here.
http://plnkr.co/edit/sy2m0RuXRudGvjnmfC3Y?p=preview

Categories