I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];
Related
Purpose
I am trying to get admin and customer show in different stages, admin can post the data after clicking the toggleShowDiv(), which allows customer to see the data.
Question
How to pass !isAdmin() into ng-if? Currently, I am only getting isAdmin as default.
Is able to post it into table TD by TD (row by row)? not sure, I am writing the correct code here.
My thought
Can I use ng-if to each single TD = isAdmin() or !isAdmin, and control by a click function?
$scope.showDiv = isAdmin();
$scope.toggleShowDiv = function (auction) {
var title = 'text.......';
var text = 'are you sure?';
ConfirmModal(title, text, function () {
$scope.showDiv = !isAdmin() ;
});
};
HTML
<div ng-if="showDiv">
<tbody class="auction-group" ng-repeat="a in foos">
<td ng-if="isAdmin()">
<input type="checkbox" ng-click="toggleShowDiv()" />
</td>
</div>
Update
isAdmin() is just a function that passed from the backend.
function isAdmin() {
return !!($aScope.currentUser && $aScope.currentUser.isAdministrator);
}
Please note: the question is not about the isAdmin() function, it works fine. What I want to do is to use a click function to show and hide the table row.
Have a look at this. Here you have 2 users online at the same time, dude1 (admin) and dude2 (non admin). You can toggle the display from the admin side for the non admin side by having a call to the back end that continuously checks if the display is valid or not. For putting a toggle on the table rows you need to just add the ng-if to the <tr> elements.
var app = angular.module('app', []);
app.controller("controller", function($scope) {
$scope.dude1 = {admin: true, name: [{name: 'A+', country:'India', publish: true}, {name: 'A', country:'Unknown', publish: true}]};
$scope.dude2 = {admin: false, name: [{name: 'A+', country:'India', publish: true}, {name: 'A', country:'Unknown', publish: true}]};
$scope.toggler = (index) => {
$scope.dude1.name[index].publish = !$scope.dude1.name[index].publish;
};
$scope.names = (dude) => {
return dude.name;
};
setInterval(() => {
/**
* Any backed function to get and repopulate the data.
* Update the value of publish from the server. I'm just using
* the other guys data. But you should fetch it from the server.
*/
$scope.dude2 = valfromServer();
// console.log($scope.dude2, $scope.dude1);
}, 2000);
var valfromServer = () => {
return {
admin: false,
name: $scope.dude1.name
};
};
$scope.publish = (dude, index) => {
return dude.admin || dude.name[index].publish;
};
$scope.isAdmin = (dude) => {
return dude.admin;
};
});
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.6.0" data-semver="1.6.0" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
</head>
<body ng-app="app" ng-controller="controller">
<span>Admin Panel</span>
<div>
<table style="width:40%">
<tr ng-repeat="x in names(dude1)" ng-if="publish(dude1, $index)">
<td>{{ x.name }}</td>
<td>{{ x.country }}</td>
<td>{{ $index }}</td>
<td><button ng-click="toggler($index)" ng-if="isAdmin(dude1)">Publish</button></td>
</tr>
</table>
</div>
<hr>
<span>Non admin panel</span>
<div>
<table style="width:40%">
<tr ng-repeat="x in names(dude2)" ng-if="publish(dude2, $index)">
<td>{{ x.name }}</td>
<td>{{ x.country }}</td>
<td>{{ $index }}</td>
<td><button ng-click="toggler($index)" ng-if="isAdmin(dude2)">Publish</button></td>
</tr>
</table>
</div>
</body>
</html>
<div ng-if="showDiv == true || isAdmin == true">
<tbody class="auction-group" ng-repeat="a in foos">
<td ng-if="isAdmin == true">
<input type="checkbox" ng-click="toggleShowDiv()" />
</td>
</div>
JS code Let say first any one who enters will be customer
$scope.showDiv = false;
$scope.isAdmin = false;
now when response comes form backend check the response and change the value of $scope.isAdmin accordingly.
if(response == admin){
$scope.isAdmin= true;
}else{
$scope.isAdmin = false;
}
now in onclick checkbox function
$scope.toggleShowDiv = function (auction) {
var title = 'text.......';
var text = 'are you sure?';
ConfirmModal(title, text, function () {
if($scope.showDiv == false){
$scope.showDiv = true;
}else{
$scope.showDiv = false;
}
});
};
Well I think that you should use some var that change according if the user click like $scope.showTable = true /false. But not complety sure about your real need.
I am confused on your question -
I will suggest few points, i hope it will help you -
ng-if is a inbuilt directive. You can use it on any DOM element. You can control it by using attribute or function, only need to pass Boolean attribute to this directive. Eg:
ng-if="showHideAttribute" or ng-if="functionNameWhichReturnBoolean()"
Scope - if you are clicking on button/checkbox/ng-click applied element is available in the same scope of applied ng-if directive then no problem. Otherwise you need to use service or observers (on/emit/broadcast) or rootScope then only it will work.
I hope you are receiving isAdmin = true/false from backend in your function. So, i am thinking this is the problem of scope.
Instead of ng-if="showDiv" use something link ng-if="obj.showDiv"
In the controller define $scope.obj = {};
The issue is ng-if creates its own scope, so always pass data as an object because objects in JS are passed by reference.
You can do this
ng-if = "isAdmin == false"
You're really confusing me, but if I understood correctly, it is something like this you want?
First things first, your HTML is truely horrible, parts of tables in divs? Don't do that...
Secondly, don't hack kabout with the isAdmin to toggle things.
isAdmin should only be used to check if a user is an admin.
You can however create another variable that instantiates to the same value, and use that one to toggle stuff.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
this.content = 'This is some unpublished content, only the admin can view, unless you\'ve now gone and publish it.';
this.isPublished = false;
this.isAdmin = false;
});
/* Put your css in here */
textarea,
label,
button {
display: block;
margin: 15px 15px 0 0;
}
button {
display: inline-block;
}
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
<div ng-app="plunker">
<div ng-controller="MainCtrl as $ctrl" ng-init="$ctrl.isAdmin=false">
<article>
<section class="content">
<section ng-if="$ctrl.isAdmin || $ctrl.isPublished">{{ $ctrl.content }}</section>
<section ng-if="!$ctrl.isAdmin && !$ctrl.isPublished"><pre>-- no published content found --</pre></section>
</section>
<section class="admin-only" ng-if="$ctrl.isAdmin">
<label><input type="checkbox" ng-model="$ctrl.isPublished"> publish article</label>
</section>
</article>
<hr />
<label><input type="checkbox" ng-model="$ctrl.isAdmin"> is admin</label>
</div>
</div>
edit:
You're still confusing me, but does this come closer to what you want / need?
What i understand is, "When admin trigger some action i.e. ng-click, user should be able to see that data/changes."
If this is the case, consider following:
I'm damn sure that this is not going to happen on the same machine.
Admin will be using the application on his machine from where it'll
do some action i.e. ng-click will gets fired and some data will gets
changed on server.
Now there will be (n) number of users using the application from
their machines. How they will get to know about the change which admin has made?
In such scenario, when there is changes on server and
client(browser) should be aware of that we make use of socket.io,
which listens to events from server and refreshes itself when there
is some changes on server state or we can say, when admin has
triggered some action i.e. ng-click.
Let me know, if you have any queries.
Thanks
I have an angular service that fetch contacts data from the database, I then use these data so that the user can add one contact or more to a deal, so there is a many to many relationship between the deal and contact, the selected data are displayed in a grid (syncfusion grid).
I need to have a constant list of the data retrieved from database, and a varible containing the selected contacts that I pass to the syncfusion grid, when the user add from the dropdown I add this contact to the grid and remove it from the dropdown list, and if I deleted it from the grid I return it back to the drop down list, so here's my code:
this service get the contact list:
var contactsListDB = [];
contactService.getAll().then(function (contacts) {
contactsListDB = contacts.data;
$scope.contactsList = contactsListDB; // the scope used in the select input in the HTML
});
the method that add contact:
$scope.addContact = function (contact) {
var contactJson = JSON.parse(contact);
$scope.dealContacts.push(contactJson);
var index = $scope.contactsList.findIndex(x => x.id == contactJson.id);
SyncFusionDealContact($scope.dealContacts);
$scope.contactsList.splice(index, 1);
}
this function is invoked in HTML:
<ng-form name="ContactForm">
<md-input-container>
<label>{{"Contact" | translate}}</label>
<md-select ng-model="contact">
<md-option value="{{null}}">-- {{"selectContact" | translate}} --</md-option>
<md-option ng-repeat="contact in contactsList" value="{{contact}}">{{contact.name}}</md-option>
</md-select>
</md-input-container>
<md-input-container>
<div>
<button class="btn btn-primary" type="submit" ng-disabled="!ContactForm.$valid" ng-click="addContact(contact)" aria-label="submit">{{'add' | translate}}</button>
</div>
</md-input-container>
<div id="GridDealContacts">
<script id="columnTemplateContacts" type="text/x-jsrender">
<a class="btn btn-danger btn-xs contactDelete" data-id="{{:id}}" ng-click="DeleteContact{{:id}}"><i class="fa fa-trash-o "></i></a>
<!--add delete button-->
</script>
</div>
</ng-form>
When the page is loaded I check if the object is being edited and then I exclude the existing contacts from the list comming from contacts table:
$scope.dealContacts = deal.contacts;
SyncFusionDealContact($scope.dealContacts);
execludeContacts()
execludeContacts function:
function execludeContacts() {
var exIds = [];
if ($scope.dealContacts.length > 0) {
exIds = $scope.dealContacts.map(x=> x.id)
}
var conts = contactsListDB;
conts.forEach(function (item, index) {
if (exIds.includes(item.id)) {
conts.splice(index, 1);
}
})
$scope.contactsList = conts;
}
this function handles delete action:
$scope.DeleteContact = function (id, index) {
if (id <= 0) {
$scope.dealContacts.splice(index, 1)
SyncFusionDealContact($scope.dealContacts);
}
else {
if (confirm("Are You Sure?")) {
dealService.deleteContact(id, $routeParams.id).then(function (success) {
if (success.data.isSuccess) {
SyncFusionDealContact($scope.dealContacts);
var one = contactsListDB.filter(x => x.id == id)[0];
$scope.contactsList.push(one);
$scope.dealContacts.splice(index, 1);
}
else {
alert('Cannot delete');
}
SyncFusionDealContact($scope.dealContacts);
});
}
}
}
In the code above I tried to save a copy of the contacts list in a variable that can't be changed contactsListDB, so that when a record is deleted from the grid I can get it back from this array to add it in the drop-down list again, but what happens is that the array is changed:
Screenshot:
I'm not sure of what you're asking, but I think you want to 'save' the data somewhere? If that's the case, take a look at using a factory or service. I prefer using a factory because of its simplicity.
From the short period I've worked with Angular, I know that a factory will always stay alive (even while switching views in SPA) until you refresh the page.
In a simple example, the factory will hold a list of the contacts, and have several functions like your addContact and deleteContact. This makes your code less cluttered and you just have to call the factory and its functions in your code.
I currently can't give a specific example (as I prefer using my own factories and its usage), but here you can find a comparison between factory and service and how to use them.
If I misunderstood your question, please do tell. :D
I successfully added data to array using push method normally but failed to do so inside a pop up which opens up for a particular Id .
Here is my code:
<div class="form-group">
<label class="col-sm-3" for="pwd">Speciality:</label>
<div class="col-sm-4">
<input type="text" class="form-control" ng-model="spec" id="usr">
<button type="submit" ng-click="addSpeciality()">Add </button>
</div>
<div class="col-sm-5">
<ul>
<li ng-repeat="spec in speciality">
{{ spec }}
<button ng-click="removeSpeciality($index)">Remove</button>
</li>
</ul>
</div>
</div>
Controller code:
$scope.speciality=[];
$scope.addSpeciality = function(){
$scope.speciality.push($scope.spec);
$scope.spec = '';
};
$scope.removeSpeciality = function(index) {
$scope.speciality.splice(index, 1);
};
This is point when you need a Factory. Forget about storing any data in controllers. Really - FORGET! The only proper way to share data across application is to define Factory you like and inject it separately in every place you gonna work with that data.
You should not store data in $scope. $scope itself is an instance bind to DOM element. So only way you access any data from any level of deepness is using $parent what is a great mistake.
Your controller should $inject that Factory and call related methods when you need to change anything in data. Never pass business logic in controllers. They are like Strategy design pattern - place where you only tell what business logic should take place when you got trigger (click for example):
SpecialityFactory.addSpeciality = function () {
SpecialityFactory.specialities.push({});
}
SpecialityFactory.removeSpeciality = function (idx) {
SpecialityFactory.specialities.splice(idx, 1);
}
So it will be much easier to share business logic across your controllers:
// PageController
PageController.prototype.addSpeciality = function () {
SpecialityFactory.addSpeciality();
};
PageController.$inject = ['SpecialityFactory'];
// ModalController
ModalController.prototype.removeSpeciality = function (idx) {
SpecialityFactory.removeSpeciality(idx);
};
ModalController.$inject = ['SpecialityFactory'];
Well, first of all #Apperion is right, read his answer
but to get your code working try to add the controller to your wrapper HTML element
<div class="form-group" ng-controller="ExampleController">
and
with this array your ng-repeat won't work this way, you need to use this way:
<li ng-repeat="spec in speciality track by $index">
I have an array of tasks. They have titles and and labels.
function Task(taskTitle, taskType) {
this.title = taskTitle;
this.type = taskType;
}
$scope.tasks = [];
I end up declaring a bunch of tasks with different types and adding them to the array
In my html, I show a column of cards, filtered by type of task:
<div ng-model="tasks">
<div class="card" ng-repeat="abc in tasks track by $index" ng-show="abc.type==0">
<p> {{ abc.title }} </p>
</div>
</div>
I want to bind the first card displayed in this filtered view to some other div. I'll be processing an inbox, so I'll whittle this list of cards down to zero. Each time I 'process' a card and remove it from the list, I need the data to refresh.
<div ng-model="firstCardInFilteredArray">
<h4>Title of first card:</h4>
<p> This should be the title of the first card! </p>
</div>
My intuition was to do something like this (in javascript):
// pseudo-code!
$scope.inboxTasks = [];
for (i=0; i<tasks.length(); i++) {
if (tasks[i].type == 0) {
inboxTasks.append(tasks[i]);
}
}
and somehow run that function again any time the page changes. But that seems ridiculous, and not within the spirit of Angular.
Is there a simple way in pure javascript or with Angular that I can accomplish this conditional binding?
You can filter your ng-repeat: https://docs.angularjs.org/api/ng/filter/filter
<div ng-model="tasks">
<div class="card" ng-repeat="abc in filteredData = (tasks | filter: {type==0}) track by $index">
<p> {{ abc.title }} </p>
</div>
</div>
Additionally, by saving the filtered data in a separate list you can display the next task like this:
<div>
<h4>Title of first card:</h4>
<p> filteredData[0].title </p>
</div>
Your data will automatically update as you "process" tasks.
The other answers helped point me in the right direction, but here's how I got it to work:
HTML
<input ng-model="inboxEditTitle" />
JS
$scope.filteredArray = [];
$scope.$watch('tasks',function(){
$scope.filteredArray = filterFilter($scope.tasks, {type:0});
$scope.inboxEditTitle = $scope.filteredArray[0].title;
},true); // the 'true' keyword is the kicker
Setting the third argument of $watch to true means that any changes to any data in my tasks array triggers the watch function. This is what's known as an equality watch, which is apparently more computationally intensive, but is what I need.
This SO question and answer has helpful commentary on a similar problem, and a great fiddle as well.
More on different $watch functionality in Angular
To update inboxTasks, you could use $watchCollection:
$scope.inboxTasks = [];
$scope.$watchCollection('tasks', function(newTasks, oldTasks)
{
for (i=0; i<newTasks.length(); i++)
{
if(newTasks[i].type == 0)
{
$scope.inboxTasks.append(tasks[i]);
}
}
});
I have an array of users, I want to have my ng-repeat ordered by last name when first loaded. After a new user is added have the ng-repeat ordered by dated added then last name. Essentially I want the newest users pushed to the top of the ng-repeat.
<th ng-click="menuFilter('lastName', 1);">
<div ng-class='{"menuSort":sortColumn==1}'>Name <span ng-show="share.orderByField == 'lastName'">
</div>
</th>
<tr ng-repeat="user in users | orderBy:orderByField:reverseSort"></tr>
In my JS...
_this.sortColumn = 1;
_this.orderByField = 'lastName';
_this.reverseSort = false;
_this.menuFilter = function(section, column) {
_this.orderByField = section;
_this.reverseSort = !_this.reverseSort;
_this.sortColumn = column;
};
//my attempt to reset the order by created at date
if( _this.isRefreshing ) {
_this.orderByField = ['createdAt', 'lastName'];
}
Basically this code is not doing anything. I think I am missing a step in the HTML.
Thanks!
I think this is easiest done by sorting the array in pure javascript and then using a ng-repeat without the orderBy attribute.
HTML:
<div ng-repeat="user in users">{{user}}</div>
<input type="text" ng-model="name"></input>
<button ng-click="addName()">Add name</button>
JS:
$scope.users = ["Rusty", "Shackleford", "Dale", "Gribble"];
$scope.users.sort();
$scope.addName = function() {
$scope.users.unshift($scope.name);
}
JSFiddle: http://jsfiddle.net/asWF9/2/
This answer may help to sort your array: https://stackoverflow.com/a/6712080/3675149
Try using "unshift" instead of 'push' to add an item into the array. The unshift in js enables us to insert an item to the top of an array.