I started with this simple plunkr
From what I can tell it only uses one component. I have a project that uses multiple components. I have a cart and users clicks add button to add items to said cart. The check out component just empties the cart. I expect that when the cart is empty, the display also empties and the total would show 0.00, but that is not happening. From what I can tell the HTML only changes page load not on change of data and that is the problem, but it was my understanding that angular would take care of this on its own.
Thanks in advance
Relevant code:
app.config.js (this file has the factory and checkout functionality)
'use strict';
angular.
module('floorForceApp').
config(['$routeProvider', '$provide',
function config($routeProvider,$provide) {
$routeProvider.
when('/home', {
template: '<home-page></home-page>'
}).
when('/floors', {
template: '<floor-page></floor-page>'
}).
when('/cabinets', {
template: '<cabinet-page></cabinet-page>'
}).
when('/walls', {
template: '<wall-page></wall-page>'
}).
when('/checkout', {
template: '<checkout-page></checkout-page>'
}).
otherwise('/home');
},
]).factory('floorForceCart',function(){
let total = 0;
let addedItems = [];
// let additem = function(item,price){
// }
return{
addItems:function(item,count){
let exist =false;
$.each(addedItems,function(i,v){
if(v.itemNo === item.itemNo){
exist = true;
v.count = v.count + count;
total = total + (item.itemPrice*count);
}
});
if(!exist){
let toPush = {};
toPush.itemNo = item.itemNo;
toPush.count = count;
toPush.itemName = item.itemName;
toPush.itemPrice = item.itemPrice;
addedItems.push(toPush);
total = total + (item.itemPrice*count);
}
console.log("Cart:",addedItems);
console.log("Total:",total);
},
removeItems: function(item,count){
$.each(addedItems,function(i,v){
if(v.itemNo === item.itemNo){
v.count = v.count - count;
total = total - (item.itemPrice * count);
if(v.count == 0){
addedItems.splice(i,0);
}
}
});
},
getTotal:function(){
return total;
},
getCart:function(){
return addedItems;
},
checkout:function(){
total = 0;
addedItems = [];
console.log("Check out successful.");
console.log("Total:",total,"Cart:",addedItems);
alert("Checkout Successful!");
}
};
});
checkout-page.component.js (data is loaded from factory to here)
'use strict';
angular.
module('checkoutPage').
component('checkoutPage',{
templateUrl: 'checkout-page/checkout-page.template.html',
controller: function checkOutController($scope,$http,floorForceCart){
let self = this;
$scope.total = floorForceCart.getTotal();
$scope.cart = floorForceCart.getCart();
$scope.checkOut = function(){
floorForceCart.checkout();
}
}
})
checkout-page.html (this page displays the checkout)
<div>
<div style="height:30em;">
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-sm-4 h-100 ">
<div class="row prodImage h-100"></div>
</div>
<div class="col-sm-8 h-100 ">
<div class="row h-100">
<div class="checkOutTitleDiv titleDiv">Checkout</div>
<div class="checkOutCartDiv paddingZero">
<div ng-repeat="item in cart" class="row marginAuto cartItemRow">
<div class="itemNameDiv col-sm-5">{{item.itemName}}</div>
<div class="itemPriceDiv col-sm-3">{{item.itemPrice|currency}}</div>
<div class="itemQuantityDiv col-sm-4">
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-4 itemQuantity">{{item.count}}</div>
<div class="col-sm-4"></div>
</div>
</div>
</div>
</div>
<div class="checkOutButtonDiv paddingZero">
<div class="row h-100 marginAuto">
<div class="col-sm-4 cartTotalDiv">
<div class="">Total:{{total|currency}}</div>
</div>
<div class="col-sm-4"></div>
<div class="col-sm-4">
<input class="checkOutButton btn btn-success" ng-click="checkOut()" type="button"value="Check Out"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
It is because in the controller you add a property in the $scope referencing the same array of items referenced by the addedItems variable in the factory:
$scope.total = floorForceCart.getTotal();
$scope.cart = floorForceCart.getCart();
Then, when you call checkout from the factory, you re-assign the addedItems variable from the factory to a new array, and you assign a 0 to the total variable. The problem is that the properties $scope.total and $scope.cart doesn't have any way of knowing this. $scope.cart will still be pointing to the old array with items.
You could solve this by either:
Changing your $scope.checkOut to
$scope.checkOut = function(){
floorForceCart.checkout();
// And refresh your $scope
$scope.total = floorForceCart.getTotal();
$scope.cart = floorForceCart.getCart();
}
Or by instead of assigning a new array to addedItems in the factory, clearing it using:
addedItems.length = 0;
If you go with the last approach, you would still have to do $scope.total = floorForceCart.getTotal(); after floorForceCart.checkout(); to update the total in your $scope.
Related
I'm not confident what I'm trying to do is correct, so please give me a "more correct" option if the whole premise is wrong.
I have an AngularJS 1.5.11 application. I am trying to create a generic modal html tag that can be used throughout the application wherever a modal is required. I have a page template that looks like this:
<form class="page-sidebar-container" data-name="mainForm">
<div class="page-sections">
<div class="form-columned form-columned-1">
<div class="form-row">
<div class="section column column-0 span-0" id="section-JobFromType">
<h4 class="section-heading">From Type</h4>
<div class="columns gutters-large">
<div style="display: table;margin: 18px 0px;">
<api-select label="Type" items="fromTypeItems" item="selectedFromType" required>
</api-select>
</div>
</div>
</div>
<div class="section column column-1 span-0" id="section-JobToType">
<h4 class="section-heading">To Type</h4>
<div class="columns gutters-large">
<div style="display: table;margin: 18px 0px;">
<api-select label="Type" items="toTypeItems" item="selectedToType"
read-only="toTypeDisabled" required>
</api-select>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<api-modal is-open="entitySelectOpen" label="{{entitySelectLabel}}">
<modal-body>
{{$id}}
<api-select label="{{entitySelectParentLabel}}" items="entitySelectParentItems" item="selectedEntitySelectParent" required>
</api-select>
<api-select label="{{entitySelectChildLabel}}" items="entitySelectChildItems" item="selectedEntitySelectChild" read-only="entitySelectChildDisabled" required>
</api-select>
<api-select label="{{entitySelectEntityLabel}}" items="entitySelectEntityItems" item="selectedEntity" read-only="entitySelectEntityDisabled" required>
</api-select>
</modal-body>
<modal-actions>
{{$id}}
<api-button label="{{modalPrimaryActionLabel}}" type="primary" icon="icon-checkmark" on-click="modalPrimaryActionClick"></api-button>
<api-button label="Return" type="return" icon="icon-cancel" on-click="modalReturnActionClick"></api-button>
</modal-actions>
</api-modal>
with the following controller:
(function (angular) {
angular.module('views.jobcontrol', [
'ngRoute',
'components.formApiControlSelect',
'components.formApiControlText',
'components.formApiModal'
])
.config([
'$routeProvider',
function ($routeProvider) {
'use strict';
var route = {
templateUrl: 'modules/apiViews/jobcontrol/jobcontrol-view.html',
controller: 'JobControlController',
reloadOnSearch: false,
caseInsensitiveMatch: true
};
$routeProvider
.when('/view/jobcontrol/:action/:jobNumber/?', route);
}
]).controller('JobControlController', [
'$scope',
'$timeout',
'$routeParams',
function ($scope, $timeout, $routeParams) {
'use strict';
function generateMockGuid() {
var result, i, j;
result = '';
for (j = 0; j < 32; j++) {
if (j == 8 || j == 12 || j == 16 || j == 20)
result = result + '-';
i = Math.floor(Math.random() * 16).toString(16).toUpperCase();
result = result + i;
}
return result;
function option(label,value){
return {
value:value,
label:label
}
}
$scope.fromTypeItems = [option("test",1)];
$scope.selectedFromType = null;
$scope.$watch('selectedFromType', function (){
console.log("do something");
});
/* to type */
$scope.fromTypeItems = [option("test",1)];
$scope.selectedToType = null;
$scope.toTypeDisabled = true;
$scope.$watch('selectedToType', function () {
console.log("do something else");
});
/* entity select modal */
$scope.selectedFromEntity = null;
$scope.selectedToEntity = null;
$scope.entitySelectParentItems = [option("parent 1", generateMockGuid()),option("parent 2", generateMockGuid()),option("parent 3", generateMockGuid())];
$scope.entitySelectChildItems = [option("child 1", generateMockGuid()),option("child 2", generateMockGuid()),option("child 3", generateMockGuid())];
$scope.entitySelectEntityItems = [option("entity 1", generateMockGuid()),option("entity 2", generateMockGuid()),option("entity 3", generateMockGuid())];
$scope.selectedEntity = null;
$scope.$watch('selectedEntity', function () {
console.log('selectedEntity has changed to ' + $scope.selectedEntity);
});
function clearModalSelections(){
console.log($scope);
$scope.selectedEntitySelectParent = null;
$scope.selectedEntitySelectChild = null;
$scope.selectedEntity = null;
}
$scope.modalPrimaryActionLabel = "Next";
$scope.modalPrimaryActionClick = function(){
clearModalSelections();
$scope.entitySelectOpen = false;
}
$scope.modalReturnActionClick = function(){
clearModalSelections();
$scope.entitySelectOpen = false;
}
}
]);
})(window.angular);
The modal works, it has my api-button's and api-select's in it, and the api-select's items are populated properly with the 3 options added to the $scope.entitySelectParentItems, $scope.entitySelectChildItems and $scope.entitySelectEntityItems arrays. The problem is that
$scope.$watch('selectedEntity', function () {
console.log('selectedEntity has changed to ' + $scope.selectedEntity);
});
never gets called. If I move the three api-select's out of the api-modal > modal-body and up the same level as the api-modal then everything works as expected (other than he elements being on the base page, rather than in the modal), but as soon as everything is inside the modal I lose access to when the user selects a value and what that value is.
I'm sure this must be something to do with the transclude having a seperate scope, but I'm confused. How can the api-select in the modal transclude access the main scope for its items to populate a select list and even updates if the list updates, but when the user selects something it isn't bound back to the scope the same way it does for the other api-select's on the page. Am I using ng-transclude wrong? Is this an OK thing to do, but I am missing a step?
In case it matters, the javascript for api-modal looks like this:
angular.module('components.formApiModal',[
'components.formApiButton'
])
.directive('apiModal', ['$timeout', '$compile', function ($timeout, $compile) {
'use strict';
return {
restrict: 'E',
templateUrl: 'modules/apiComponents/generic/modal/modal-template.html',
controller: 'apiModalController',
transclude: {
'body': 'modalBody',
'actions': 'modalActions'
},
scope: {
isOpen: "=",
label: '#',
actions: "="
},
link: function (scope, element, attrs) {
}
};
}])
.controller('apiModalController', ['$scope', function($scope) {
}]);
and the template:
<div class="modal-backdrop modal-large" data-ng-class="{'modal-closed' : !isOpen }">
<div class="modal-container">
<div class="modal" data-ng-click="$event.stopPropagation()">
<h1 class="modal-header" >{{label}}</h1>
<div class="modal-body">
<div class="form-messages" data-ng-if="formPage.overrideConfirmationMessages.length">
<div class="form-header-errors">
</div>
</div>
<h4 class="section-heading" data-ng-if="section.altHelp">{{section.altHelp}}</h4>
<div class="columns gutters-large">
<div ng-transclude="body"></div>
</div>
</div>
<div class="modal-footer" >
<div ng-transclude="actions"></div>
</div>
</div>
</div>
</div>
I have been attempting to perform an ng-repeat inside an ng-repeat collapse. My inner ng-repeat, repeats the entire array inside of every div. I have tried to track by index but still no resolve:
<div class="col-md-4">
<div id="markets">
<div class="panel panel-default" ng-repeat="m in marketResults | limitTo:5">
<div class="panel-heading" ng-click="m.isCollapsed = !m.isCollapsed">{{m.marketname}}
</div>
<div uib-collapse="!m.isCollapsed">
<div ng-repeat="s in stuff" >
<p>{{s.$$state.value.Address}}</p>
<p>{{s.$$state.value.GoogleLink}}</p>
<p>{{s.$$state.value.Products}}</p>
<p>{{s.$$state.value.Schedule}}</p>
</div>
</div>
</div>
</div>
Inside Controller:
_getLocation: function(key) { //extract latlng from _recordsCache
var latLong = this._recordsCache[key];
// console.log(latLong);
fmCoordinates.lat = latLong.lat;
fmCoordinates.lng = latLong.lng;
var promise = requestMarkets(fmCoordinates.lat,fmCoordinates.lng);
promise.then(function(marketData) {
$scope.marketResults = marketData.results; //receiving market data
$scope.quantity = 5; //limit market data to 5
$scope.marketInfo = [];
$scope.getInfo = function(){
return $scope.marketInfo;
}
for(var property in $scope.marketResults) {
var id = $scope.marketResults[property].id;
console.log(id);
$scope.marketInfo.push(getDetails(id));// brings back the details
};
console.log($scope.getInfo());
}, function(reason) {
console.log('Failed: ' + reason);
});
if( this._recordsCache.hasOwnProperty(key) )
return latLong;//then after use .loc attribute
else
return false;
},
You need to add the second array as a property of each item in first array. See below code:
HTML:
Instead of writing ng-repeat = "s in stuff", we will write ng-repeat = "s in m.stuff"
<div class="col-md-4">
<div id="markets">
<div class="panel panel-default" ng-repeat="m in marketResults | limitTo:5">
<div class="panel-heading" ng-click="m.isCollapsed = !m.isCollapsed">{{m.marketname}}
</div>
<div uib-collapse="!m.isCollapsed">
<div ng-repeat="s in m.stuff" >
<p>{{s.$$state.value.Address}}</p>
<p>{{s.$$state.value.GoogleLink}}</p>
<p>{{s.$$state.value.Products}}</p>
<p>{{s.$$state.value.Schedule}}</p>
</div>
</div>
</div>
</div>
Controller:
First load marketResults array. Then in for loop, loop through each element in marketResults and load the second array using id and save it as stuff property for each element, so we can access second array using m.stuff in ng-repeat
_getLocation: function(key) { //extract latlng from _recordsCache
var latLong = this._recordsCache[key];
// console.log(latLong);
fmCoordinates.lat = latLong.lat;
fmCoordinates.lng = latLong.lng;
var promise = requestMarkets(fmCoordinates.lat,fmCoordinates.lng);
promise.then(function(marketData) {
$scope.marketResults = marketData.results; //receiving market data
$scope.quantity = 5; //limit market data to 5
for(var market in $scope.marketResults) {
market.stuff = getDetails(market.id);// brings back the details
};
}, function(reason) {
console.log('Failed: ' + reason);
});
if( this._recordsCache.hasOwnProperty(key) )
return latLong;//then after use .loc attribute
else
return false;
},
You can use ng-init to keep children on each parent row. Pass id to a getChild(m.id) and get child and then use it on second loop.
ng-init="rowchild = getChild(m.id)"
<div id="markets">
<div class="panel panel-default" ng-repeat="m in marketResults | limitTo:5" ng-init="rowchild = getChild(m.id)">
<div class="panel-heading" ng-click="m.isCollapsed = !m.isCollapsed">
{{m.marketname}}
</div>
<div uib-collapse="m.isCollapsed">
<div ng-repeat="s in rowchild">
<p>{{s.Address}}</p>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div id="markets">
<div class="panel panel-default" ng-repeat="m in marketResults | limitTo:5">
<div class="panel-heading" ng-click="m.isCollapsed = !m.isCollapsed">{{m.marketname}}
</div>
<div uib-collapse="!m.isCollapsed">
<div>
<p>{{m.stuff.$$state.value.Address}}</p>
<p>{{m.stuff.$$state.value.GoogleLink}}</p>
<p>{{m.stuff.$$state.value.Products}}</p>
<p>{{m.stuff.$$state.value.Schedule}}</p>
</div>
</div>
</div>
</div>
</div>
//Inside Controller
_getLocation: function(key) { //extract latlng from _recordsCache
var latLong = this._recordsCache[key];
// console.log(latLong);
fmCoordinates.lat = latLong.lat;
fmCoordinates.lng = latLong.lng;
var promise = requestMarkets(fmCoordinates.lat,fmCoordinates.lng);
promise.then(function(marketData) {
$scope.marketResults = marketData.results; //receiving market data
$scope.quantity = 5; //limit market data to 5
$scope.marketInfo = [];
console.log($scope.marketResults);
for (var i = 0; i < marketData.results.length; i++){
marketData.results[i].stuff = getDetails(marketData.results[i].id);
};
}, function(reason) {
console.log('Failed: ' + reason);
});
if( this._recordsCache.hasOwnProperty(key) )
return latLong;//then after use .loc attribute
else
return false;
},
I've created a simple app that share data between controllers. A button increment a variable number value in a controller (A) and then it shares data with other controller (B)
Factory can't return primitives variables, right? So I declared an object. The problem is that getList() can't return the object property data.product, it is always an empty string.
When I return the full object, it works fine. scope.total in ControllerB is {"product":x}
What would be the best way to achieve this? I only need to share the variable scope.total from one controller to the other one, not the full object.
HTML
<div ng-app="tabsApp">
<div id="London" class="tabcontent">
<div ng-controller="tabOneController as vm">
<input type="button" value="add" ng-click="vm.addMore()"/>
</div>
</div>
<div id="Paris" class="tabcontent">
<div ng-controller="tabTwoController as vm">
<span>{{vm.total}}</span>
</div>
</div>
</div>
JS
angular
var app = angular.module('tabsApp', []);
app.controller("tabOneController", controllerA);
app.controller("tabTwoController", controllerB);
app.factory('myData', function() {
var data = {
product: ''
};
function addItem(value) {
data.product = value;
}
function getList() {
return data;
}
return {
addItem: addItem,
getList: getList
};
});
function controllerA(myData){
var scope = this;
scope.total = 0;
scope.addMore = function(){
scope.total++;
myData.addItem(scope.total);
}
}
function controllerB(myData){
var scope = this;
scope.total = myData.getList();
}
try this way:
plnkr here: https://plnkr.co/edit/Bpf1RSKhsRr92Qi7168q?p=preview
<div ng-app="tabsApp">
<div id="London" class="tabcontent">
<div ng-controller="tabOneController as vm">
<input type="button" value="add" ng-click="vm.addMore()"/>
</div>
</div>
<div id="Paris" class="tabcontent">
<div ng-controller="tabTwoController as vm">
<span>{{vm.total.product}}</span>
</div>
</div>
</div>
then inside the .js
var app = angular.module('tabsApp', []);
app.controller("tabOneController", controllerA);
app.controller("tabTwoController", controllerB);
app.service('myData', function() {
var data = {
product: 0
};
this.addItem = function (value) {
data.product = value;
}
this.getList = function() {
return data;
}
});
function controllerA(myData){
var scope = this;
scope.total = 0;
scope.addMore = function(){
scope.total++;
myData.addItem(scope.total);
}
}
function controllerB(myData, $scope){
this.total = myData.getList();
}
How can i pass html through in AngularJS controller ?
Here is my list.html:
<div class="col-xs-3" ng-repeat="item in companyData">
<a ng-click="getPackageInfo({{item.iCompanyID}},'{{item.vCompanyName}}')" class="block panel padder-v bg-primary item">
<span class="text-white block">{{item.vCompanyName}}</span>
</a>
<div id="packagehtml"></div>
</div>
<div id="lp" class="col-md-12 listing-div hidden"></div>
in controller.js:
$scope.pData = [];
$scope.getPackageInfo = function(id,name) {
$scope.name = name;
var summery = SubscriptionoptioncompanylistFactory.getSummary(id);
document.getElementById("lp").classList.remove("hidden");
$('.packages-data').html('');
$('#loading').show();
SubscriptionoptioncompanylistFactory.getPackageInDetail(id).
success(function(data) {
if(data != 0) {
$("#lp").html(summery); // this is used to append the data
document.getElementById("np").classList.add("hidden");
Array.prototype.push.apply($scope.pData, data);
$('#loading').hide();
} else {
document.getElementById("lp").classList.add("hidden");
document.getElementById("np").classList.remove("hidden");
$('#loading').hide();
}
});
};
Here, I have wrote $("#lp").html(summery);, in that div I have to append html which comes from var summery = SubscriptionoptioncompanylistFactory.getSummary(id);. But this is not going to append the data. In console I can see that data comes in summary variable. How can I do?
have a look at below modifications
Use angular ng-show for showing/hiding elements
Use data binding and avoid Jquery like Dom manipulation
<div class="col-xs-3" ng-repeat="item in companyData">
<a ng-click="getPackageInfo({{item.iCompanyID}},'{{item.vCompanyName}}')" class="block panel padder-v bg-primary item">
<span class="text-white block">{{item.vCompanyName}}</span>
</a>
<div id="packagehtml"></div>
</div>
<div id="lp" ng-show="lbVisible" class="col-md-12 listing-div hidden">{{summaryBinding}}</div>
and the controller would look like :
$scope.pData = [];
$scope.getPackageInfo = function (id, name) {
$scope.name = name;
var summery = SubscriptionoptioncompanylistFactory.getSummary(id);
$scope.lbVisible = true; //document.getElementById("lp").classList.remove("hidden");
$('.packages-data').html('');
$scope.loadingVisible = true; //$('#loading').show();
SubscriptionoptioncompanylistFactory.getPackageInDetail(id).
success(function (data) {
if (data != 0) {
$scope.summaryBinding = summery; // $("#lp").html(summery); // this is used to append the data
$scope.npVisible = false; // document.getElementById("np").classList.add("hidden");
Array.prototype.push.apply($scope.pData, data);
$scope.loadingVisible = false; // $('#loading').hide();
} else {
$scope.lbVisible = false; //document.getElementById("lp").classList.add("hidden");
$scope.npVisible = false; //document.getElementById("np").classList.remove("hidden");
$scope.loadingVisible = false; // $('#loading').hide();
}
});
};
your snippet is not showing elements that you use :
np, #loading so just find them and add the `ng-show` with the proper scope variable : `npVisible , lbVisible , loadingVisible`
and note that we add the data using summaryBinding
hope this helps :)
Has anyone been able to pause / resume sync using Angular fire?
New to the Firebase/ AngularJS (Angularfire) stuff.
I've try nulling the objects and re initializing them on the click of a button but it doesnt do anything,
Thanks for any input.
Steve
Code as requested
var app = angular.module("imageanalyser-app", ["firebase"]);
var ref = null;
function MyController($scope, $firebase) {
ref = new Firebase("...");
$scope.messages = $firebase(ref.endAt().limit(100));
}
function resumeSync() {
$("#btnResume").click(function() {
ref = new Firebase("...");
});
}
function stopSync() {
$("#btnStop").click(function () {
ref = null;
});
}
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div ng-app="imageanalyser-app">
<div id="links" class="container-fluid" ng-controller="MyController">
<div ng-repeat="msg in messages" class="gallery-item-container">
<a href="{{msg.highResUrl}}" data-gallery>
<img ng-src="{{msg.Source}}" height="20" width="20" style="position: absolute"/>
<img ng-src="{{msg.imgUrl}}" class="gallery-item-image" />
</a>
<div class="gallery-item-tags" ng-repeat="tag in msg.tags">{{tag}}</div>
</div>
</div>
</div>
</div>
You're updating ref, but never changing $scope.messages. The ng-repeat is based on $scope.messages, so it won't change either!
To get this to behave as you want you'll need to affect the messages variable.
This worked for me, using the $off() to stop sync and then reinitialize it.
var fireBaseRef;
var synced = true;
function MyController($scope, $firebase) {
fireBaseRef = new Firebase("......");
$scope.messages = $firebase(fireBaseRef.endAt().limit(100));
jQuery("#btnToggleState").click(function () {
if (synced) {
$scope.messages.$off();
synced = false;
} else {
$scope.messages = $firebase(fireBaseRef.endAt().limit(100));
synced = true;
}
});
}