In a previous question here, I am trying to show a date only when it has changed between 2 messages.
The difference is that I am now connected to a realtime database (Firebase), so I subscribe to the data source and pass it through the async pipe before going through index and *ngIf:
<div *ngFor='let message of (chat | async) ; let i = index'>
<button *ngIf="i == 0 || (message.timestamp | date: 'ddMMMMyyyy') != ((chat | async)[i-1].timestamp | date: 'ddMMMMyyyy')">
{{ message.timestamp | date:'ddMMM' }}
</button>
...
<!--More html elements below (omitted)-->
...
</div>
This works well when I first load the view, however, if I push a new entry into chat, I get the following error:
TypeError: Cannot read property '0' of null
Maybe I am not too sure how the async pipe works, but when I try returning {{ (chat | async).length }}, it works as intended. Any suggestions on a workaround/proper practice?
In case someone else finds this since it's the first result with the key words, I was able to get the index of a *ngFor with the async pipe like this (assuming messages is an Observable of an iterable):
<div *ngFor="let message of (messages | async); let i = index;">
Still not sure how the AsyncPipe can be manipulated here, but I did mange to find a workaround that involves not using the pipe. I will await a better answer hopefully before marking this closed.
I subscribe to the data source in my class, and manipulate the array with a for loop before displaying it.
The (template) code in my question has now become:
<div *ngFor='let message of messages; let i = index'>
<button *ngIf="message.isNewDay">
{{ message.timestamp | date:'ddMMM' }}
</button>
...
<!--More html elements below (omitted)-->
...
</div>
And in the controller:
private chatid: string //chat id passed from previous screen
private chat: FirebaseListObservable<any>;
private messages: any = [];
constructor(private firebase: FirebaseService,
private _datepipe: DatePipe) {
}
ionViewLoaded() {
this.chat = this.firebase.database.list('/chat-messages/' + this.chatid);
this.chat.subscribe((data) => {
this.messages = data;
for (let i: number = 0; i < this.messages.length; i++) {
if (i === 0)
this.messages[i].isNewDay = true;
else {
let date1 = this._datepipe.transform(this.messages[i].timestamp, 'ddMMMMyyyy');
let date2 = this._datepipe.transform(this.messages[i-1].timestamp, 'ddMMMMyyyy');
if (date1 !== date2)
this.messages[i].isNewDay = true;
else
this.messages[i].isNewDay = false;
}
}
});
}
Note that I am currently using DatePipe in the class code (as well as in the template), so it is necessary to use providers: [DatePipe] along with pipes: [DatePipe].
If you want to use *ngFor, AsyncPipe, and index together - you should use this construction:
<ul>
<li *ngFor="let person of obsPersons | async as persons; index as i">
{{i + 1}} out of {{persons.length}}: {{person.personId}}
</li>
</ul>
Tested and works on Angular 7.
As mentioned above by #Primal Zed let company of (filteredCompaniesAliases | async); let i = index" should do the trick, syntax sample with plain html table:
<table>
<tr *ngFor="let company of (filteredCompaniesAliases | async); let i = index"">
<td>
{{selectedDiscountableCharge.name}}
</td>
<td>
${{selectedDiscountableCharge.amount}}
</td>
<td>
${{selectedDiscountableCharge.amount - ( (selectedDiscountableChargePercentanges / 100) * selectedDiscountableCharge.amount ) }}
</td>
</tr>
</table>
in your component:
companyAliasFilterCtrl: FormControl;
filteredCompaniesAliases: Observable<any[]>;
...
this.filteredCompaniesAliases = this.companyAliasFilterCtrl.valueChanges
.pipe(
startWith(''),
map(company => company ? this.filterOperatorsAliases(company) : this.companies.slice())
);
I use a function for showing (or not) the date. So to show the date (in this example) only when it changes:
<ion-list no-lines>
<ion-item-sliding *ngFor="let trip of tripsExt$ | async; let i = index">
<ion-item>
<ion-row (click)="change(trip)">
<ion-col col-6>
{{showOnChange(trip.date)}}
</ion-col>
<ion-col col-6>
{{trip.end_address_name}}
</ion-col>
</ion-row>
</ion-item>
<ion-item-options side="left">
<button ion-button color="primary" (click)="delete(trip)">
Delete
</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
With showOnChange:
showOnChange(currentDate) {
if (currentDate != this.previousDate) {
this.previousDate = currentDate
return currentDate
}
return ''
}
Related
*)Please check out this 2 images in this i have a stock-shift array it's length is 2 and inside every
array i have another array, in the inside array i have to sum the elements of the quantity
*)check my html code and ts file and help me with the solution
*)Check the third image Ui there I need to bind sum value before the quantities like that for every
tile (box) i need to get different value but it is updating last value in all tiles (boxes)
html
====
<div *ngFor="let data of dataSource; let i = index;">
<!-- Stockshift card -->
<mat-card *ngIf="type === 'shift'" class="mb-3 card" (click)="addParams(data,data?.invoiceNumber,5, i)">
<mat-card-content class="p-2" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="space-between">
<div><span [ngClass]="{'text-success' : data?.isSyncSuccess, 'text-danger' : data?.isSyncFailed, 'text-warning': (!data?.isSyncFailed && !data.isSyncSuccess)}"><b>#{{ data?.invoiceNumber }}</b></span></div>
<div><span [ngClass]="{'text-success' : data?.isSyncSuccess, 'text-danger' : data?.isSyncFailed, 'text-warning': (!data?.isSyncFailed && !data.isSyncSuccess)}"><b>{{ data?.entryDate | date: 'dd MMM yyyy'}}</b></span></div>
</div>
<span class="text-dark pt-2 card-content">
<span>{{(data?.type === 'TO') ? 'Sent' : 'Received'}}</span>
<span *ngIf="data && data?.productList[0]?.quantity < 1"> product <span class="text-primary">({{data?.productList[0]?.quantity}} Quantity)</span> {{(data?.type === 'TO') ? 'to' : 'from'}} </span>
<span *ngIf="data && data?.productList[0]?.quantity > 1"> products <span class="text-primary">({{totalProductList}} Quantities)</span> {{(data?.type === 'TO') ? 'to' : 'from'}} </span>
<span class="text-primary"> {{ data?.shiftGodown?.outletName }} </span>
</span>
</mat-card-content>
</mat-card>
</div>
Ts file
======
get totalProductList() {
let quantity: number;
this.transactionData.stockShift.forEach(stock => {
quantity = stock.productList.reduce((stock, list) => {
return stock + list.quantity;
}, 0)
});
return quantity;
}
1 solution - if you want to use getter totalProductList you can create a component for your box and in this case getter will work as expected.
stock.component.ts for example
get totalProductList() {
return this.stock.productList.reduce((stock, list) => {
return stock + list.quantity;
}, 0)
});
}
2 solution (BAD, cause call function in template) - you need to transform you getter to function and pass one box element to work correctly
ts
getTotalProductList(stock) {
return stock.productList.reduce((stock, list) => {
return stock + list.quantity;
}, 0)
});
}
html
<span class="text-primary">({{getTotalProductList(data)}} Quantities)</span>
3 solution - also transform you getter to function and call it for each element when you receive data and save to element property (stock .quantitiesSum for example, and use in html {{stock .quantitiesSum}})
p.s.
I think you should compare productList.length instead productList[0].quantity here
<span *ngIf="data && data?.productList[0]?.quantity < 1">
to
<span *ngIf="data && data?.productList.length < 2">
In my application I have saved the data when we click on it(we can add the multiple data by entering some data and save the multiple data by clicking the save button).
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(habits);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
In the above code when we click on delete button it will show some popup having buttons yes(implemented in above code) and now so my requirement is when we clcik on yes button in from the popover it has to delete the particular one.
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
public deletedata(habits){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category ==categoryDetails.category) {
isCategoryExist = true;
this.selectedCategoryDetails.splice(habits, 1);
}
});
}
}
The above code I have written is for saving the data(we can enter multiple data and save multiple )
Like the above I have to delete the particular one when we click on yes button from the popover.
Can anyone help me on the same
If you're iterating in your html like:
<... *ngFor="let categoryDetails of selectedCategoryDetails" ...>
and your button with deletedata() is in the scope of ngFor, you can:
Change your iteration to include index of an item and trackBy function for updating the array in view:
<... *ngFor="let categoryDetails of selectedCategoryDetails; let i = index; trackBy: trackByFn" ...>
On the button click pass the index to deletedata() method like:
deletedata(index)
Create your deletedata method like:
deletedata(index:number){
this.selectedCategoryDetails.splice(index, 1);
// other code here, like calling api
// to update the selectedCategoryDetails source
// etc.
}
Create trackByFn method like:
trackByFn(index,item){
return index;
}
EDIT: Without index
If you want to iterate over selectedCategoryDetails in the ts file, without using ngFor with index in your html, you can have your deletedata like this:
deletedata(categoryDetails:any){
for (let i = this.selectedCategoryDetails.length - 1; i >= 0; i--) {
if (this.selectedCategoryDetails[i] === categoryDetails.category) {
this.selectedCategoryDetails.splice(i, 1);
}
}
}
It will iterate over selectedCategoryDetails backwards and remove the categoryDetails if it finds it in the array of objects.
Now, you only need to pass the categoryDetails to deletedata in your html:
(click)="deletedata(categoryDetails);customHabitPopovers.hide()"
I have a messaging component that makes it possible to write messages with another user in my system. The problem is when I open the component the site gets super slow and it takes up to 15 seconds before the messages are loaded. What could I do different in the code to make it work as it should?
allMessages() {
return this.messages.concat(this.uploadingMessages);
}
<ng-container #messageList *ngFor="let m of allMessages(); let i = index; trackBy: trackByMessageId">
<div *ngIf="case !== null" [class.unread]="case.hasReadUntilTimestamp < m.timestamp && !m.sender.isMe && !m.setRead">
<div class="newMessage">
<div class="newMessage__line"></div>
<div class="newMessage__line"></div>
</div>
<div class="row messageAndHeader line" *ngIf="allMessages()[i - 1]?.sender.id !== m.sender.id; else elseBlock">
<div class="row messageInfo">
<div class="profileImg">
<img *ngIf="m.sender.profileImageUrl !== null" [src]="m.sender.profileImageUrl" />
*ngIf="m.sender.profileImageUrl === null">
</div>
In angular DOM will be refreshed for all events, so here allMessages() will be called repeatedly, SO do this operation in NgOnInit() assign to one variable like below
ngOnInit(){
this.allMesage = this.messages.concat(this.uploadingMessages);
}
and in compoent.html modify the code as below
<ng-container #messageList *ngFor="let m of allMessage; let i = index; trackBy: trackByMessageId">
if the messages are getting populated after making API call then add async pipe like below
<ng-container #messageList *ngFor="let m of allMessage | async; let i = index; trackBy: trackByMessageId">
And remove calling method In ngif
<div class="row messageAndHeader line" *ngIf="allMessage[i - 1]?.sender.id !== m.sender.id; else elseBlock">
If it is api call modify the code in ngOnInit() like below
ngOnInit(){
apiCall.subscribe(data=>{
this.allMesage = data.messages.concat(data.uploadingMessages);
},
error=>{
console.log(error)
})
I'm trying to use the angular2-counto directive inside an *ngFor loop.
I know that I need a unique string in each instance. So
<div
counto
[step]="30"
[countTo]="myArr[0].Amount"
[countFrom]="0"
[duration]="2"
(countoChange)="counto_0 = $event"
(countoEnd)="onCountoEnd()">
{{counto_0 | number:'1.0-0'}}
</div>
<div
counto
[step]="30"
[countTo]="myArr[1].Amount"
[countFrom]="0"
[duration]="2"
(countoChange)="counto_1 = $event"
(countoEnd)="onCountoEnd()">
{{counto_1 | number:'1.0-0'}}
</div>
...
Should become
<div *ngFor="let item of myArr"
counto
[step]="30"
[countTo]="item.Amount"
[countFrom]="0"
[duration]="2"
(countoChange)="??? = $event"
(countoEnd)="onCountoEnd()">
{{??? | number:'1.0-0'}}
</div>
But what can I use in the ??? as a unique param for each iteration of the loop? The counto directive needs a string param (I think). Attempts to use *ngFor=let i item of myArr; let i = index; and then somehow use the index in the directive don't work.
<div *ngFor="let item of myArr; let i = index;"
counto
[step]="30"
[countTo]="item.Amount"
[countFrom]="0"
[duration]="2"
(countoChange)="i = $event"
(countoEnd)="onCountoEnd()">
{{i | number:'1.0-0'}}
</div>
Results in Uncaught Error: Cannot assign to a reference or variable!
You are trying to assign $event to the i variável.
Try something like:
newVar[i] = $event
I have an event form that where I get information put it in the localstorage (I cannot use an API) and I want to take the startdate and enddate out of localstorage, change them so they appear as dd-mm-yyyy (not yyyy-mm-ddT00:00:00.000Z) The issue is getting them out of the array and setting them. I have no idea where to begin....
Here is my JavaScript/jQuery:
.controller("Events", function($scope, $location) {
let URL = "https://morning-castle-91468.herokuapp.com/";
$scope.authToken = localStorage.getItem("authToken");
$scope.username = localStorage.getItem("username");
$scope.events = [];
let lastEvent = 0;
while (localStorage.getItem("event"+lastEvent)){
$scope.events.unshift(JSON.parse(localStorage.getItem("event"+lastEvent)));
lastEvent++;
}
JSON.parse(localStorage.getItem("event")) || [];
$scope.submitForm = function() {
if ($scope.eventForm.$valid) {
$scope.event.username = $scope.username;
localStorage.setItem("event"+lastEvent, JSON.stringify($scope.event));
}
};
})
And here is my HTML:
<div class="row eventcontent" ng-repeat="event in events">
<div class="col-xs-12 eventpost">
<div class="row-flex">
<div class="col-xs-8 ">
<h4>{{ event.title }}</h4>
</div>
<div class="col-xs-4 ">
<h4>{{ event.name }}</h4>
</div>
<div class="col-xs-12 ">
<td>{{ event.description }}</td>
<br />
<br />
<p>{{ event.address }} <br /> {{ event.country }}</p>
<br />
<p>{{ event.status }}</p>
</div>
<div class="col-xs-12 ">
<p>{{ event.startdate }} {{ event.enddate }}</p>
</div>
</div>
</div>
</div>
To get Data from localstroage which will be an JSON ArrayList then,
var retrievedData = JSON.parse(localStorage.getItem("event"+lastEvent));
var setDateFormat = $filter('date')(new Date((retrievedData[1].substr(6)), 'dd-mm-yyyy'));
I'm not used to Angular.
But anyway, it looks like you are creating an array events having all the previously stored event.
You use unshift which is adding each event at the beginning of the array. The result probably is like so (for 2 elements):
$scope.events:
[ // That is event1
{
name:someValue,
description:someValue,
address:someValue,
country:someValue,
status:someValue,
startdate:"yyyy-mm-ddT00:00:00.000Z",
enddate:"yyyy-mm-ddT00:00:00.000Z"
},
// That is event0
{
//Same structure as above...
}
]
So now,, assuming you wish to have the last event, which is the first element in the events array, and the structure is as above, I would try:
// Retreive the dates from the first array element... Only the part before the "T" in each string.
var retreived_start = $scope.events[0].startdate.split("T")[0];
var retreived_end = $scope.events[0].enddate.split("T")[0];
// Split the date by the "-" to re-order...
var start_parts = retreived_start.split("-");
var end_parts = retreived_start.split("-");
// YYYY-MM-DD results
var formatted_start = start_parts[2] +"-"+ start_parts[1] +"-"+ start_parts[0];
var formatted_end = end_parts[2] +"-"+ end_parts[1] +"-"+ end_parts[0];
// Place it in your Angular controller array (? -- I'm unsure here)
$scope.event.startdate = formatted_start;
$scope.event.enddate = formatted_end;
But now that you've modified the event array... It may be stored as "formatted"... So be aware of that!
That line is doing absolutely nothing: JSON.parse(localStorage.getItem("event")) || [];, since not assigned to a variable, you can safely remove it.