While working on the phone tutorial i wondered how to implement this use case
browser renders a list of phones
user clicks on add phone for one of these phones
the function addItem is fired and adds this phone into the array phonesInCart
the list with phones in cart $scope.phonesInCart = []; is updated
My codepen demo has the same logic as this code
<body ng-controller="PhoneListCtrl">
<h3>Phones we sell</h3>
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
<b>{{phone.name}} </b>
<button ng-click="addItem(phone)"
ng-disabled="{{phone.orderReadonly}}">add phone</button>
</li>
</ul>
<h3>Phones in your cart</h3>
<ul>
<li ng-repeat="phoneInCart in phonesInCart">
{{phoneInCart.name}}
</li>
</ul>
</body>
and relevant javascript
$scope.phonesInCart = [];
$scope.addItem = function(phone) {
// these lines have no effect on the ui
phonesInCart.push(phone);
$scope.$apply();
}
Current status
The list is rendered and the 'addItem' function is fired.
Currently the list of the phones in cart are not updated / not rendered.
My question
Could you explain
what has to be done to fill a second array phonesInCart[] and refresh another ng-repeat and
do i have to create / use more than one controller phonecatApp.controller('PhoneListCtrl' ... to be able to have a secon ng-repeat?
In your code, instead of phonesInCart.push(phone), you need to do:
$scope.phonesInCart.push(phone)
Since that is the variable you loop over in your html.
EDIT Since the addItem call happens as a part of an ng-click directive, you can get rid of $scope.$apply. Actually, you should be getting an error saying 'digest loop already in progress' with your current code.
I edited your codepen code slightly.
First off, as others already pointed out, you missed a $scope when pushing into the array of phones.
Secondly, if you want to be able to add multiple of the same phones to the list, you need to add track by $index to your ng-repeat, so it accepts "duplicates".
You also had an unnecessary $scope.apply in your addPhone function - you don't need to update it manually; when you push to the scope variable array, it triggers the digest cycle itself.
Lastly, you don't need to $watch the phonesInCart array due to the same reason as above.
Here's an updated pen to which all these changes have been made. If you have anything else to ask, please do :)
Related
I was trying to build a multi-product application.
Using a drop-down in the header, the user can choose which application data to be shown.
Below is the code snip it of my code of my drop-down in header after every click/ change will call the function change Product (passing the selected product name)
<ul class="dropdown-menu pull-right" style="margin-left:-100px;" ngbDropdownMenu aria-labelledby="languageDropDown">
<li *ngFor="let a of products " class="dropdown-item text-center" (click)="changeProduct(a)"style="cursor:pointer;" data-toggle="collapse" data-target=".navbar-collapse" (click)="isCollapse = !isCollapse">
{{a.name}}
</li>
</ul>
Here is the function code
Explanation of below code. In this function, I want the values of other function should be changed. I was thinking to call ngOnInit() but seems like the changes are not being reflected until the page is being reloaded manually. Hence i had to use window.location.reload() to reload function but this is not an idea solution .
changeProduct(product:Product) {
this.app=product.name;
this.cookieService.set('currentAppName',""+product.productCode);
this.cookieService.set('productID',product.id);
this.OtherComponent.ngOnInit();
window.location.reload()
}
Hope someone can suggest me the correct way how to change the other component values after making changes in header in Angular 13
So, here is a minimal example. The "MyService" hold the value (simple a string in this example). In the AppComponent we bind the value from the service with a Subscription. This Subscription can be in every component/child route what ever you want. So if the value in the service has changed all components get the new value.
Here is the Stackblitz link.
Greetings, Flo
This question already has an answer here:
Problems with `track by $index` with Angular UI Carousel
(1 answer)
Closed 4 years ago.
I am having an issue with AngularJs ng-repeat and angular-bootstrap-switch
I am using:
ng-repeat="item in items track by $index"
When I added a new item into the first index of array by:
newItem = {};
items.splice(0, 0, newItem);
The DOM contain a bs-switch:
When a new item added it reuse the first element in array base on $index, so it doesn't re-render (that's what I get from this docs). The problem I have is the previous element has switch-on.
The DOM effect issue I have is the class "switch-on" doesn't refresh on new item and it keep on.
Expected: In this case I want to switch is off instead of on like the image. Cause it's an empty object
P/s: Cause of the business so
I cannot add default value for that Switch. It need to be an empty object
I also cannot use any identifier of the item object to track replace to $index
If I use default track by $id it will cause some business issue also
TEMP SOLUTION FOR WHO WORKING ON Angular 1.5 or Upper:
With angular 1.5 and you can use angular-bootstrap-switch 0.5.1
It will fixed the issue, after frapontillo release a changed: "Make "switch-change" trigger when model changes"
Thank you so much for supporting.
What I perceive from your question is that you want to add a new item to main array and when It renders, you want it's switch to be off by default.
Well there are couple of things you need to keep in mind.
ng-repeat only behaves as a loop in our DOM.
If you want to have the switch off by default, you must need to have a model for that in every item of the main array. It'll keep track of every switch's state in DOM.
Here's what you should try
From your controller:
newItem = {switchState: 0};
items.splice(0, 0, newItem);
From your HTML:
<div ng-repeat="item in items track by $index">
<!-- your Name and Decription input fields.. -->
<input
bs-switch
ng-model="item.switchState" />
So I think you're ignoring ng-model of angular-bootstrap-switch which causes you the problem.
I'm trying to do a search with product results using AngularJS. I've obtained an JSON object with my results": example below
[{"store_id":"17","user_id":"29","company_name":"Liquor R Us","company_type":"Alcohol",
"phone":"(303) 555-5555","website":"http:\/\/liqourrus.com","address":"5501 Peoria St",
"address_2":"","city":"Denver","state":"CO","zip":"80239","lat":"39.796181",
"lng":"-104.84863","mon_open":"","mon_close":"","tues_open":"","tues_close":"",
"wed_open":"","wed_close":"","thurs_open":"","thurs_close":"","fri_open":"","fri_close":"",
"sat_open":"","sat_close":"","sun_open":"","sun_close":"","distance":"1.1668156112981596",
"s":"s","st":"Every Store"}]
I'm using ng-repeat="store in storeResult" and the results will not display until I click on my Filter function, please note the filter is not applied!
<div class="product" ng-repeat="store in storeResult">
{{store.company_name}}
</div>
ng-click="setFilter('all');"
$scope.setFilter = function(filter) {
if(filter == 'all') {
$scope.searchProduct.product_type = '';
$scope.searchStore.company_type = '';
}
}
If I click the "setFilter" button, all results show. I'm trying to figure out how to make it display without having to click the button.
This work around only works in Chrome. Firefox and IE, never display results.
Plunker: link
My best guest is that by clicking the setFilter button you are triggering a digest cycle and your ngRepeat will be executed because of that. I suspect that you are assigning your storeResult outside the digest cycle and that's the reason is not displaying initially. I cannot tell for sure because is not in your description how is that JSON assigned to your storeAssignment .
Can you check how is that variable assigned?
Can you change the way it's and do this:
$timeout(function() {
$scope.storeAssignment = _your_json_value;
});
You will have to include the $timeout dependency but with this, a change in storeAssignment will be inside your digest cycle and the ngRepeat will see that change.
Hope that helps.
It looks like you're not setting the filter to 'all' until you click the filter function. Make sure that filter starts out as 'all'.
I have a simple Angular app which has some data, and an input box to filter that data.
<input class="form-control" ng-model="filters.generic" />
<h3>{{filteredUsers.length}} filtered users</h3>
<ul>
<li ng-repeat="user in filteredUsers = (data.userData | filter:filters.generic )">
{{user.registration.firstName}}
</li>
</ul>
The 'filteredUsers' variable stores the users with the applied filter. That all works absolutely fine. However, I'd like to be able to add a watch to execute a particular function whenever this 'filteredUsers' variable changes (whenever the user types anything in the input box).
I do not want to simply add a method to ng-change on the input, because I want to introduce other filters elsewhere.
I thought that I could do this in my controller with
$scope.$watch('filteredUsers', function () {
alert('something');
});
This executes once, when the application loads, and then never again. What must I do to make this watch for changes to filteredUsers?
You might need to use $watchCollection() instead of $watch()
Keep in mind that the filter on an ng-repeat could be running way more frequently than you need or expect it to, and adversely affect the performance of your code.
Even though you said you don't want to use ng-change, I think you should reconsider that and only update your array when needed.
<input class="form-control" ng-model="filters.generic" ng-change="genericFilterChange()" />
var updateFilteredUsers = function(){
$scope.filteredUsers = $filter('filter')($scope.data.userData, $scope.filters.generic);
alert('something');
};
var genericFilterChange = function(){
updateFilteredUsers();
updateOtherFilteredThings();
};
I am using AngularJS to create a page which contains a list of products that shows information such as a name, price, region etc. This is displayed kind of like an accordion with the name in the header and extra information in the body.
Since there could be a large amount of these items displayed I am using dirPagination (https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination) to paginate these items. My markup at the moment looks like this:
<div class="custom-list" dir-paginate="asset in assets | itemsPerPage: 10 track by $index">
<div class="custom-list-item" ng-class="{'tag-hover': isHoveredTag(asset)}">
<div class="custom-list-item-heading" ng-click="toggleShowAssetDetails(asset)">
<p class="col-lg-5">{{ asset.name }}</p>
<div class="col-lg-offset-2 col-lg-3 rating">
<rating value="asset.rating" />
</div>
<button ng-click="addAsset(asset)"><span class="glyphicon glyphicon-plus"></span></button>
<div class="clearfix"></div>
</div>
<div class="custom-list-item-content" style="display: none" animate="shouldShowAssetDetails(asset)">
...
</div>
</div>
</div>
As you can see I'm using paginate in a pretty standard way just looping through the items in an array and displaying 10 per page. I also have a directive called rating which looks at a value called rating in the item. This is a number from 1 - 5 which is used to display a star rating system next to the name. The directive looks like this:
var rating = function ($compile) {
return {
restrict: "E",
scope: {
value: "="
},
link: function (scope, element, attrs) {
scope.$watch(attrs.rating, function () {
for (var i = 1; i <= 5; i++) {
if (i <= scope.value) {
var starElement = angular.element("<span class=\"icon icon-crown\"></span>");
$compile(starElement)(scope);
element.append(starElement);
} else {
var emptyStarElement = angular.element("<span class=\"icon-empty icon-crown\"></span>");
$compile(emptyStarElement)(scope);
element.append(emptyStarElement);
}
}
})
}
}
}
This looks at the value and inserts the icons based on the value of rating (e.g if the rating was 2 the directive would insert two icon-crown icon spans and 3 icon-empty icon-crown icon spans.
This was working perfectly fine before I included the pagination. However now it will only work for the first 10 items. When you change the page, the rating will not change and just keep the same icons from the previous page, even if the values are different.
I understand this is because the directive sets everything at the beginning and it will not run when you change page because the items aren't reloading they are just being shown and hidden again. But the directive is manipulating the DOM so it doesn't update when the page changes.
The problem is I don't know how to resolve this. I thought about changing the directive to look for the pagination current page instead but then I don't have access to the current list item.
I'd appreciate any help on getting the directive to update the icons when the page is changed.
Update
Here's a link to a Plunker project showing the problem I'm having: http://plnkr.co/edit/VSQ20eWCwVpaCoS7SeQq?p=preview
This is a very stripped down version of the section on my app that I'm having an issue with. There's no styling included although I have kept the CSS class structure. I've also changed the icons to use bootstrap ones just to simplify the Plunker project.
The functionality is the same however. If you go from page 1 to page 2 notice how the stars remain the same despite that fact that the asset rating values are different. However if you go to page 3 and back to page 2 or 1 they will change. The reason this happens is because there are less items on page 3 and therefore when you go back to page 1 or 2 the remaining items will be called again to retrieve the rating values.
You simply need to remove or replace track by $index.
Tracking by $index will give you the following behavior:
There is an array of max 10 length that represents the items to show. The first item will have index 0.
You go to the next page and the items in the array are replaced.
The first item in the array will still have index 0. Since you are tracking by index and the index has not changed, AngularJS will not recreate the DOM node representing the item. Since the DOM node isn't recreated, the directive will not execute this time and the rating will not update.
If you go from page 3 to page 2, the directive will execute for the 7 last elements on page 2, since they didn't exist on page 3, so they are considered new this time.
If you really need to use track by you should use a property that is actually related to the object (and unique), for example:
dir-paginate="asset in assets | itemsPerPage: 10 track by asset.name"
Demo: http://plnkr.co/edit/A80tSEliUkG5idBmGe3B?p=preview