I have a backend API written in C#, and my Angular App has a service layer of angular components between this API and the visual components. 3 components in particular are using a shopping cart component service. They are:
A shopping cart icon on a navmenu component - it shows a count of the number of items in the cart.
A cart summary page - shows items in the cart and allows you to increase/decrease the qty of an item or delete it from the cart entirely using the quantity component (3).
Quantity object. This is attached to a product page as well as line items in the cart. It can increase or decrease the number of items in the cart for the product page the component sits on.
The exposed methods for the cart service are:
//Add item to cart or increment qty of item by 1
addToCart(product: Product): Observable<ShoppingCart> { ... }
//Decrement qty of item by 1 or delete if qty = 0
removeFromCart(product: Product): Observable<ShoppingCart>{.... }
//Get all items in cart
getCartItems(): Observable<ShoppingCart>{ ...}
// Delete all items in cart
async clearCart(): Promise<Observable<ShoppingCart>>{ ... }
// Delete entire cart
async removeCart(): Promise<void> { ... }
Because the service is detached from the API and injected into each of the components separately (product component, quantity component and navmenu component), clicking the plus() or minus (-) button on the quantity component is updating the quantity object, but the navmenu is not getting updated as the numbers change.
For instance, I have this on my cart component,
<ng-container *ngIf="cart$ | async as cart">
<div class="card">
<div class="card-body">
<h6 class="card-subtitle card-mb-4 card-tb-4 text-muted">
You have {{cart.itemCount}} items in your cart.
with the following code in the .ts file
ngOnInit() {
this.getCart();
}
async getCart() {
this.cart$ = await this.cartSvc.getCartItems();
}
The navmenu component has this code in the page
<li class="nav-item">
<a class="nav-link" routerLink="/cart">
<span class='fa fa-shopping-cart'></span>
<span *ngIf="cart$ | async as cart">
<span class="badge badge-warning badge-pill" *ngIf="cart.itemCount">{{ cart.itemCount }}</span>
</span>
</a>
</li>
with this code in the .ts file, my intent being that the number of items should increase in the badge-pill when I clicked the + on a product to add it to the cart.
async ngOnInit() {
// No need to unsubscribe as only 1 instance in DOM for lifetime of application
this.cart$ = this.cartSvc.getCartItems();
}
I passed Observable as return results in all my cart functions to update the local variables in the quantity component and the product component - this is causing them to update and re-render properly, but the update never bubbles up to affect the navmenu, so the only way I can get the quantity there to update is to refresh the page.
Why does the cart count, an observable I am subscribing to, not update when I add items to the cart? Forgive my ignorance, I am new to Angular and still trying to get a handle on all its features.
Thanks in advance!
Your service should have a behavior subject for the cart and then the methods on the service should be updating the data in the cart. You should not be returning observables from the update methods.
cart$ = new BehaviorSubject<ShoppingCart>({ products: [] });
addToCart(product: Product) {
const cart = this.cart$.value;
this.cart$.next({ ...cart, products: [ ...cart.products, product] });
}
removeFromCart(product: Product) {
const cart = this.cart$.value;
this.cart$.next({ ...cart, products: cart.products.filter(p => p.id !== product.id) });
}
clearCart() {
const cart = this.cart$.value;
this.cart$.next({ ...cart, products: [] });
}
and in your component
cart$ = this.cartSvc.cart$;
Related
I am bringing in data from an api call and outputting the data to html inside a template string using variables from the main.js file. All of that works fine. The problem that has me blocked is I want to have an add to favorites button that a user can click and add the title to a favorites list. When I add the button inside the template literal the addEvenListener I have for the button is null and if I add the button to the index.html I cannot access the data from the api. I am trying to store the data first into firestore database after the user clicks the button. Then output the data into a dropdown list.
I've added a collection to the firestore database and can display the data from the backend to the favorites list but I need to grab the data from the front end, store it on the back end, and display it in the favorites list.
function getMovie(){
let movieId = sessionStorage.getItem('movieId');
// Make a request for a user with a given ID
axios.get("https://api.themoviedb.org/3/movie/" + movieId + "?
api_key=redacted")
.then(function (response) {
console.log(response)
let movie = response.data;
//console.log(movie);
let output = `
<div class="dropdown">
<button class="dropbtn" id="dropbtn">Favorites</button>
<div id="myDropDown" class="dropdown-content"></div>
</div>
`;
$('#movie').html(output);
})
.catch(function (error) {
console.log(error);
});
}
addFavorite.addEventListener('submit', (e) => {
e.preventDefault();
firebase.firestore().collection('favorites').add({
Title: addFavorite['movieid'].value
}).then(() => {
//close
console.log(addFavorite)
})
})
Not sure if I need to do another api call for this functionality or not. I did one api call to get a list of movies then another to get one movie. When the user goes to the one movie that is where I want the add favorite button. I hope someone knows how to lead me in the right direction. First time asking on Stackoverflow don't hurt me lol
i am taking simple ul list as example
<ul id="movie">
</ul>
<script>
// DUMMY records
const apiCallDataExample = [
{
id: 1,
name: "A"
},
{
id: 2,
name: "B"
},
{
id: 3,
name: "C"
}
];
let movies = [];
getMovie();
function getMovie() {
movies = apiCallDataExample; // You will call your API to get the data, and store it in upper scope 'movies' variable.
let htmlContent = ''; // prepare your html content -> in your case it is droup down i guess.
for (let movie of movies) {
htmlContent += `
<li>
<span> ${movie.name} </span>
<button onclick="addToFavourite(${movie.id})"> click me </button>
</li>
`
// Attach click event or any listener to get selected movie id or any identifier
}
let elem = document.getElementById("movie").innerHTML = htmlContent;
}
/**
* click event will trigger and we can fetch selected movie by given identifier, in my case it is `id `field.
*/
function addToFavourite(id) {
let selected_movie = movies.find(movie => movie.id === id); // find your movie by id.
console.log("selected movie is", selected_movie)
// add your data into collection as per your defined property , my case { id, name}.
/*
// Your fire base function
firebase.firestore().collection('favorites').add({
id: selected_movie.id,
name: selected_movie.name
}).then(() => { })
*/
}
</script>
I have a parentComponent called dashboardComponent. This contains a table.
When selecting a row in that table I display it's subcomponent, which is companyAdmin, and I pass the rowID to it.
CompanyAdmin on it's turn contains 4 subComponents. The task of the companyAdminComponent is to do a GET request with the rowId it received from it's parentComponent, the dashboardComponent. Then send distribute that data to it's 4 subComponents.
The problem I am having is, this is all on 1 page and so can't use a resolver. So when I am rendering the subComponent of companyAdminComponent it crashes as it didn't receive the data back yet from the GET request. and getting the error
Cannot read property 'name' of undefined
So basically how do I show the subComponent of companyAdmin, only after the data has been loaded.
The code
dashboard.html (Parent component)
Only showing the relevant code for brevity
// ap-company-admin only is being shown when a row is selected in the table
<div class="row extraRowSpace" *ngIf="companySelected">
<div class="col-xl-12">
<ap-company-admin [companyId]="company.id"></ap-company-admin>
</div>
</div>
CompanyAdmin HTML (subComponent of dashboardComponent)
<ap-company-details [companyDetails]="companyDetails"></ap-company-details>
CompanyAdminComponent TS (subComponent of dashboardComponent)
ngOnInit() {
this.companyService.getAllCompanyDetails(this.companyId).subscribe(
response => {
this.allCompanyDetails = response;
this.companyDetails = {
cardsName: this.allCompanyDetails.cardsName,
name: this.allCompanyDetails.name
};
}
);
}
CompanyDetailsComponent (subComponent of companyAdminComponent)
#Input() companyDetails;
companyName: FormControl;
cardsName: FormControl;
ngOnInit() {
this.companyName = new FormControl(this.companyDetails.name);
this.cardsName = new FormControl(this.companyDetails.cardsName);
// Basically it is crashing here because on init of this component the data of companyDetails hasn't come back yet from the GET call
}
All the things I found are things to do with resolver, but as I am not navigating but only show and hiding the companyAdminComponent when selecting a row, I don't think I can do this with a resolver. Any advice?
I am building (as an exercise) a shopping cart in vue.js 2. I have my shop items and order items stored in my vue data array and a button rendered in a for loop for each shop item to add the item to the order (ex. push).
Here is the section of code that houses my list of items from my shop array in my vue data:
<fieldset>
<legend>Shop</legend>
<section v-if="shop">
<article v-for="(item, index) in shop">
<header><h1>{{ item.title }}</h1></header>
<p>{{ item.description }}</p>
<footer>
<ul>
<li>${{item.price}}</li>
<!-- find a way to set input name -->
<li><label>Quantity <input type="number" name=""></label></li>
<li><button v-on:click="addItemToOrder($event)">Add to Order</button></li>
</ul>
</footer>
</article>
</section>
<p v-else>No Items to Display</p>
</fieldset>
here is my vue element:
new Vue({
el: '#my-order',
data:{
'shop':[
{
'title':'Website Content',
'description':"Order your Website content by the page. Our search-engine-optimized web content puts you ahead of the competition. 250 words.",
'price':25,
'sku':'web001'
},
{
'title':'Blog Post',
'description':"We write blog posts that position your website as a go-to resource for expert knowlegde.",
'price':50,
'sku':'blog001'
},
{
'title':'Twitter Post'
},
{
'title':'Product Description'
}
],
'customizations':null,
'order':{
'items':null,
'total':null
},
'customer':null,
'payment':null
},
methods:{
addItemToOrder: function(){
/* Here is where I need to append the item to the cart */
}
}
})
How do I pass the item in the for loop to the order (eg: append it to order.items)?
You just need to pass the item in as a parameter to the function.
v-on:click="addItemToOrder(item)"
Then you can use it your Vue component
addItemToOrder: function(item){
this.order.items.push(item);
}
Make sure you initialize order.items to an empty array inside your components data so that you can push to it.
'order':{
'items': [],
'total': 0
},
In general, it is a good idea to initialize your data to the correct data type if you know what it will be.
I realise this is a bit late however in case anyone else happens across this thread...
You need to pass in the event as well as the item
in your vue code
someMethod : function(e, item){}
in your html
<a v-on:click="someMethod($event, $data)"></a>
I must say I'm still pretty new to Vue.js.
I build a Point of Sale system with the help of this tutorial.
I have made some modifications to it, for example the items are
fetched with an api call and you now can edit the price of an item in the transaction. It also required some fixing after updating to latest vue version, for example v-repeat to v-for etc.
Now I have a problem that is if you edit the price of an item in the transaction
it also changes the price of the item in the item list where you click to add the items.
Due to reasons I can't provide a whole image of the app or large parts of code.
Here's a screencap trying to explain what's happening
In the item list template the items are rendered from items array with v-for.
In the transaction template the items are rendered from itemList array with v-for.To clarify: itemList array contains the items added to the transaction.
I can't understand why this is happening or better yet how to prevent it. I can consider providing parts of the code if you need it.
EDIT:
In the comments Hector guessed that I'm referencing the same array in both of the templates, but that is not the case.
In the item list component I reference the items array by passing a prop.
<item-list :items="items" :add="onItemClick"></item-list>
In the transaction component I reference the lineItems array by passing a prop.
<transaction :items="lineItems" :edit-number-of-items="editNumberOfItems" :edit-price-each="editPriceEach" :edit-total-qty="editTotalQty" :edit-taxed-price="editTaxedPrice" :editing="editing" :remove-item="removeItem"></transaction>
The first three cells of the transaction table:
<tbody>
<tr v-for="item in items">
<!-- Code -->
<td>{{ item.item.id }}</td>
<!-- Name -->
<td>
<span>{{ item.item.name }}</span>
</td>
<!-- Price Each -->
<td>
<span class="editable-item" v-if="!item.editingPriceEach" #click="editPriceEach(item)">{{ item.item.priceEach }}</span>
<input v-if="item.editingPriceEach" #keyup.enter="editPriceEach(item)" type="text" v-model="item.item.priceEach" autofocus>
<button v-if="item.editingPriceEach" #click="editPriceEach(item)">OK</button>
</td>
In <tr v-for="item in items"> items is referring to the prop name items not the array called items in the data option.
Also for example the function editPriceEach passed to the transaction component as a prop looks like this:
editPriceEach: function (lineItem) {
lineItem.editingPriceEach = !lineItem.editingPriceEach;
}
As you can see it triggers the editingPriceEach property which is then used in the onItemClick function. It's used to add the items from the item list to the transaction:
onItemClick: function (item) {
var found = false;
for (var i = 0; i < this.lineItems.length; i++) {
if (this.lineItems[i].item === item) {
this.lineItems[i].numberOfItems++;
found = true;
break;
}
}
if (!found) {
this.lineItems.push({
item: item,
numberOfItems: 1,
editingNumberOfItems: false,
editingName: false,
editingPriceEach: false,
editingTaxedPrice: false,
editingTotalQty: false
});
}
},
Thank you for your patience with the small amount of code.
I have some tasks to do . I want to make sure that each task has a maximumFundPossible property of $1000 constant, a currentfund property that is variable as it will be updated as tasks are being funded , a budget property that is variable but constant for each task item . That task item property has to be set to a certain amount when being created. I guess I will have to add an input field for that amount to be set during task creation ? How ?
I already have my todo tasks but I am stuck as I am a few days new to angular and I hope to fully learn it through this project.
How do I modify my code so that my todo tasks have the described above properties? Also, when I click do button or do for free button, etc, will my current views keep track of that specific task X ? How do I keep track of that task X so that my do , doforfree or any other function I will be using affects only that specific task X.
I know it s too much to ask but angularjs just has a high learning curve.
Thanks a lot for your help
Here is my todoController:
facebookExample.controller('todoController', ['$scope', function($scope) {
// Initialize the todo list array
//if local storage is null save the todolist to local storage
$scope.todoList = [];
if (localStorage.getItem("mytodos") === null)
{
localStorage.setItem("mytodos", angular.toJson($scope.todoList));
}else
{
//set the todolist from local storage
$scope.todoList = angular.fromJson(localStorage.getItem("mytodos"));
}
// Add an item function
$scope.todoAdd = function() {
//check to see if text has been entered, if not exit
if ($scope.todoInput === null || $scope.todoInput === ''){return;}
//if there is text add it to the array
$scope.todoList.push({todoText:$scope.todoInput, done:false});
//clear the textbox
$scope.todoInput = "";
//resave the list to localstorage
localStorage.setItem("mytodos", angular.toJson($scope.todoList));
};
// Each task is limited to limit total funding = $1000
//current funding ; max funding; ballance funding = $1000-current
// Variables or columns for taskX
/****************/
// Task Counter for Task X
// If task-assignment is aproved by admin
// Set Task Counter to D-63
// Send reminder to Person assigned for task X in Due Date
// Do for money button is diabled unless task is funded
// Do for free is always enabled unless task is already assigned
/**************/
//Updating state of task to done only on due date
// if Dtime > DueDate
// UserX can update task to done
// Admin is notified by email of the action
// Admin should aprove of disaprove within 3 days
// Admin can aprove to Done or Undone
/*******************/
// Admin aproves of Task X to done
// Task X is updated to state of done
//if for moeny and amount raised for task >
// Payment is initiated to Tasker if task was funded and done for money
// Payment initiated from Company to Admin if task was done for free
//Do button
$scope.do = function(){
// If current user clicks do,
// Assign the current user to the task with a state of pending admin aproval
// Counter variable for dute date is initiated with D-63
};
$scope.doForFree = function(){
// Task is assigned to user with a state of pending-aproval-of-admin- assignment-do-for-free
// Admin is notified of do-for-free-request
};
// if admin aproves user that requested to do task X for money
// task X is assigned to that user
// state of task is assigned to admin-aproved-assignment-for-money
// the User is notified of admin aproval
// Task due date is created/updated
// if admin aproves user that requested to do task X for free
// task X is assigned to that user
// state of task is assigned to admin-aproved-assignment-for-free
// the User is notified of admin aproval
// Task due date is created/updated
//fund button
$scope.fund = function(){
//Redirect current user to paypal for payment to You-Serve
// Maximum payment cannot exceed Maximum amount - what 's already funded
// Need to keep track of already funded amount
// Need to keep track of task cost/price
// If paypal payment was done successfully
// Update already funded amount
// Update maximum amount for extra funding
// update the fully funded variable/boolean
// Send task/user/amount to database
// If payment fails, take out state of being funded
};
$scope.remove = function() {
//copy list
var oldList = $scope.todoList;
//clear list
$scope.todoList = [];
//cycle through list
angular.forEach(oldList, function(x) {
//add any non-done items to todo list
if (!x.done) $scope.todoList.push(x);
});
//update local storage
localStorage.setItem("mytodos", angular.toJson($scope.todoList));
};
//The Update function
//This waits 100ms to store the data in local storage
$scope.update = function() {
//update local storage 100 ms after the checkbox is clicked to allow it to process
setTimeout(function(){
localStorage.setItem("mytodos", angular.toJson($scope.todoList));
},100);
};
}]);
And here is my view:
<div ng-app="facebookExample" view-title="Tasks">
<div ng-controller="todoController">
<h1>Tasks</h1>
<div class="item item-input-inset">
<label class="item-input-wrapper">
<!-- The actual input tag which is bound to the todoInput using ng-model -->
<input type="text" placeholder="Add New Item" ng-model="todoInput" size="100">
</label>
<!-- Our button thay will call our funtion to add a new todo item -->
<button class="button button-small" ng-click="todoAdd()">
Add Task
</button>
</div>
<div ng-repeat="x in todoList">
<li class="item item-checkbox">
<label class="checkbox">
</label>
<!-- this is the checkbox element, you will see it is bound to the done setting in the items array -->
<!-- When clicked it calls the update function to update the item to its done status -->
<input type="checkbox" ng-model="x.done" ng-click="update()"/>
<!-- this is a span tag that shows the item text, I am using ng-bind, instead of the span tag we could have used {{x.todoText}} as well -->
<button class="fund-button" style= "float: left;" ng-click="fund()">Fund</button>
<span>{{x.todoText}}</span>
<button class="doButton" style= "float: right; margin-right: 2px;" ng-click="do()">Do</button>
</li>
</div>
<!-- the remove button will call the remove function and remoave all items that are marked done -->
<button class="button button-block button-assertive" ng-click="remove()">Remove Checked Tasks
</button>
</div>
</div>:
here is a simple of example of passing an item from ng-repeat to your actions:
<div ng-controller="MyCtrl">
<input type="text" ng-model="newMessage">
<button ng-click="addMessage(newMessage)">
Add
</button>
<ul>
<li ng-repeat="message in messages">
{{message}}
<button ng-click="remove(message, $index)">remove</button>
<button ng-click="uppercase(message, $index)">uppercase</button>
<button ng-click="lowercase(message, $index)">lowercase</button>
</li>
</ul>
</div>
controller:
function MyCtrl($scope) {
$scope.newMessage = '';
$scope.messages = [];
$scope.addMessage = function(msg) {
$scope.messages.push(msg);
};
$scope.remove = function(msg, index) {
$scope.messages.splice(index, 1);
};
$scope.uppercase = function(msg, index) {
$scope.messages[index] = msg.toUpperCase();
};
$scope.lowercase = function(msg, index) {
$scope.messages[index] = msg.toLowerCase();
};
}
take a look at the jsfiddle and really understand how data is being referenced by your actions. for example, I am using the implicit $index variable to access the array of messages.
http://jsfiddle.net/heavyhorse/x4b2w84c/