The data binding between html element property and script value not synchronized - javascript

Problem description:
There is an availability calendar which displays whether a person is busy on a particular slot (each day is divided to two slots). This state is stored in the isSlotFree boolean 2d array (this array is of size 31 x 2). Initially, all values in this array are initialized to true. Then, a http get request is made to a server requesting the busy dates. Once they are received, the function setIsSlotFree() is called to set the appropriate values in the array to false. In the view (HTML file) there is a <td> element for each slot. Each of these elements update their color (using class styles) based on the boolean value stored in the relevant index of the array. The problem is that the html page does not reflect the changes made to the array after calling the setIsSlotFree() function. (ie. the html elements still see all values as true). However, when I print the array in the console right after the get request, it has changed the appropriate values to false. When any event is triggered, then only the view is updated to the correct values. What is the problem here?
Following are the relevant parts of the component.ts file
export class CalendarComponent implements OnInit {
viewDate: Date;
isSlotFree: boolean[][] = [
[true, true]
];
constructor(private http: HttpClient) {
}
ngOnInit() {
this.viewDate = new Date();
var i: number;
var j: number;
for (i = 1; i < 31; i++) {
this.isSlotFree.push([true, true]);
}
let p = new HttpParams().set('month', (this.viewDate.getMonth() + 1).toString());
this.http.get < busyDateResponse[] > ("http://localhost:51967/api/stylists/getBusyDates", {
params: p
}).subscribe(res => {
this.setIsSlotFree(res);
});
this.x = true;
console.log(this.isSlotFree);
this.viewDate = new Date();
}
setIsSlotFree(res: busyDateResponse[]): void {
var busy_date: busyDateResponse;
for (busy_date of res) {
var temp: number = (busy_date.slot == 'm') ? 0 : 1;
this.isSlotFree[busy_date.day - 1][temp] = false;
}
}
}
interface busyDateResponse {
$id: string;
day: number;
month: number;
year: number;
slot: string;
}
Following shows the relevant parts of the component.html file
<ng-template #cellTemplate let-day="day" let-locale="locale">
<div class="cal-cell-top">
<div class="cal-day-number w3-xlarge">{{ day.date | calendarDate:'monthViewDayNumber':locale }}</div>
<br>
</div>
<div *ngIf="day.inMonth && day.isFuture">
<table style="width:100%">
<tr>
<td class="calendar-slot" [ngClass]="{'w3-green': isSlotFree[day.date.getDate()-1][0], 'w3-red': !isSlotFree[day.date.getDate()-1][0]}">{{isSlotFree[day.date.getDate()-1][0]}}Morning</td>
<mat-checkbox (change)="editSelectedSlots($event)" [checked]="isSelectedSlot(day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_m')?true:false" [id]="day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_m'"
*ngIf="isSlotFree[day.date.getDate()-1][0]"></mat-checkbox>
</tr>
<tr>
<td class="calendar-slot" [ngClass]="{'w3-green': isSlotFree[day.date.getDate()-1][1], 'w3-red': !isSlotFree[day.date.getDate()-1][1]}">{{isSlotFree[day.date.getDate()-1][1]}}Evening</td>
<mat-checkbox (change)="editSelectedSlots($event)" [checked]="isSelectedSlot(day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_e')?true:false" [id]="day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_e'"
*ngIf="isSlotFree[day.date.getDate()-1][1]">
</mat-checkbox>
</tr>
</table>
</div>
</ng-template>
<div>
<mwl-calendar-month-view [viewDate]="viewDate" [events]="events" (eventClicked)="eventClicked($event)" (dayClicked)="dayClicked($event)" [cellTemplate]="cellTemplate" [refresh]="refresh">
</mwl-calendar-month-view>
<div class="w3-center">
<button mat-raised-button>Make booking</button>
</div>
</div>
Please note that <mwl-calendar-month-view> utilizes the ng-template to generate cells in a calendar.

Well I finally found the solution after reading a lot about change detection in angular. The change detection strategy is executed before running the setIsSlotFree() function.Even though the values in the array are modified, the change detection strategy is not executed when the array values are changed by this function. Therefore, change detection needs to be executed manually after writing all the changes to the array. This can be done using ChangeDetectorRef.detectChanges() at the end of the setIsSlotFree() function.
If the constructor for the component is constructor(private http: HttpClient, private ref: ChangeDetectorRef) then, the isSlotFree() function would look like,
setIsSlotFree(res:busyDateResponse[]):void {
var busy_date:busyDateResponse;
for(busy_date of res) {
var temp:number = (busy_date.slot=='m')?0:1;
this.isSlotFree[busy_date.day-1][temp] = false;
}
this.ref.detectChanges();
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

Related

Angular Count Becomes 0 When Page is Refreshed

In my code, I have a page where the user can scan some labels and gets add to a list. Every time the user scan a label, total and package count above the list gets updated. But when I refresh the page, the counts become 0 as if there is no label on the list. Here is my code. What should I do?
HTML:
<div fxLayout="row" fxLayoutAlign="start center">
<span class="pr-4">{{'Paket : ' + packageCount }}</span>
<span>{{'Adet : ' + totalCount }}</span>
</div>
TS:
constructor(){
this.calculateRemainingValue();
}
addPackage(response: IKoli, barcodeNumber: string): boolean {
this.calculateRemainingValue();
this.isSaved = false;
this.fg1.reset();
return true;
}
calculateRemainingValue() {
if (this.dataSource.data) {
this.packageCount = this.dataSource.data.length;
let totalWeight = 0;
this.dataSource.data.forEach((x) => (totalWeight += Number(x.PaketAdedi)));
this.totalCount = totalWeight;
}
}
How I know this is normal behaviour of angular.
You must save the counter in some place, for example database or you could save it as cookie or save it in localStorage/sessionStorage.
Hope I helped you!

Im trying to pass array as arguments to httpservice call, but the argumments is evaluating to empty array

Im trying to upload multiple images, hence converting the image to a base64 encoded string and also storing its metadata with an array.We store the reference to the image path to database hence functionionality is written in backend for insertion.
However ,
To process image files into base64 and store metadata i use array and trying to pass as arguments to a function but i receive empty array in service call. Could someone help me understand why and how to fix this.
the upload image is called for every iteration of for loop , WHY?
Thanks in advance .
export class ItemsDetailsComponent {
//image variables
itemImageDetails: any = [];
ItemImageURLs: any = [];
itemImageCount: number = 0;
base64image: any = [];
CustImageData: any;
itemImageData: any;
itemimagePath: any;
fileList: any = [];
newImageMetaData: any = [];
imageMetaData: any = [];
addImagePopupVisible: boolean = false;
deleteImagePopupVisible: boolean = false;
tempImageCount: number = 0;
deleteImageURL: any;
deleteImageName: any;
deleteImageConfirmPopUp: boolean;
value: any[] = [];
constructor() {
// ...
}
processFile() {
let count = 0;
for (let i = 0; i < this.value.length;
(i++, count++)) {
this.fileList.push(this.value[count]);
this.httpDataService.getBase64(this.value[count])
.then(base64img => {
this.base64image[this.tempImageCount] = base64img;
this.base64image[this.tempImageCount] = this.base64image[this.tempImageCount].split(",")[1];
this.tempImageCount++;
this.newImageMetaData.push({
"type": this.fileList[i].type,
"name": this.fileList[i].name,
"size": this.fileList[i].size
});
});
}
//want to call this function only after for loop is complete but is getting called at every iteration , WHY?
this.uploadImages();
}
uploadImages() {
if (this.newImageMetaData.length == this.base64image.length) {
//recieves expected output(the array in log) **
console.log(this.newImageMetaData);
console.log(this.base64image); **
// below service call is receiving empty array - >> [] for ** [...this.base64image] ** ** [...this.newImageMetaData] **
this.httpDataService.uploadMultipleImages(["", this.itemCode, [...this.base64image],
[...this.newImageMetaData]
])
.subscribe(status => {
if ((status != -1) && status) {
this.toastr.success(status + "Image(s) Successfully Uploaded");
this.getImag();
this.getItemImageDetails();
this.newImageMetaData = [];
this.base64image = [];
} else {
this.toastr.error("Error Uploading image" + status + " Image(s) Uploaded ");
}
this.addImagePopupVisible = false;
});
}
}
//
<div class="widget-container">
<form enctype="multipart/form-data">
<dx-file-uploader #fileUploader [multiple]="true" accept="image/*" [(value)]="value" uploadMode="useForm"></dx-file-uploader>
<div class="content">
<div *ngIf="value.length > 0">
<h4>Selected Files</h4>
</div>
<div *ngFor="let file of value">
<div class="selected-item">
Name:
<span>{{file.name}}</span><br /> Size:
<span>{{file.size}}</span>bytes<br /> Type:
<span>{{file.type}}</span><br /> Last Modified Date:
<span>{{file.lastModifiedDate}}</span>
</div>
</div>
</div>
<dx-button text="Create Product" type="submit" (onClick)="uploadImages()">
</dx-button>
</form>
</div>
<div class="options">
<div class="caption">Options</div>
<div class="option">
<dx-check-box text="Allow multiple files selection" [(value)]="fileUploader.multiple"></dx-check-box>
</div>
</div>
Assuming that dx-button is a Call To Action, try and remove the action="uploadImages()" from the form tag, eventually, it will be called once processFile() completes the iteration.
If you keep an action as well as the submit button inside a form the button click will get performed.
OR
Remove onClick from button and replace uploadImages() with processFile() in form tag.

Adding more and more <timepicker> issue. if one gets changed it changes all of them

I have a little problem, I repeat timepicker with *ngFor, but it's not working properly if I changed the time in one of them, it changes in all. And all have a different id. AN IDEA TO MAKE THE WORk PROPERLY?`
COMPONENT.HTML :
<div id="schedule" *ngFor="let i of Arr(num).fill(1)"
style="display: -webkit-inline-flex">
<timepicker id="timer" class="schedulItem" style="margin-top:-28px"
[(ngModel)]="mytime" [showMeridian]="isMeridian"
[minuteStep]="mstep" (ngModelChange)="changed()">
</timepicker>
<button (click)="addSchedule()"> + </button>
</div>
COMPONENT.TS:
Arr = Array; //Array type captured in a variable
num:number = 1;
mytime: Date;
addSchedule() {
this.num = this.num + 1 ;
var length = document.querySelectorAll('.schedul').length
var time = document.getElementById("timer");
time.id += length;
}
changed(): void {
var time = this.mytime.getHours() + ":" + this.mytime.getMinutes();
console.log(time);
}
I found the problem! the model was the problem [(ngModel)]="mytime". All time pickers to the same model and one gets changed it changes all of them.

Meteor chatbox html is stuck in string form

I'm writing a chat box into my web app and HTML posts into the chat div are not showing like I expected them too. They are displaying in string form, I would like HTML to render in chat. Not sure why they are in string form, I'm not filtering the strings at all. How could I get the HTML to be rendered?
HTML:
<div *ngFor="let x of serverObj.values">
<div *ngFor="let y of x.shouts">
<p>
<span style="font-family: initial; font-size: x-small; font-
weight: bold;">{{y.shoutTime}}</span>
<span style="font-size: small; font-weight: bold;">{{y.shoutUser}}</span>
<span>: {{y.shoutContent}}</span>
</p>
</div>
</div>
<form name="shoutbox">
<textarea
style="color: black;"
id="shoutbox_input"
#textArea
(keyup.enter)="serverObj.addShout(displayName(), textArea.value)"
(keyup.enter)="textArea.value = ''"
></textarea>
<p><button id="shout_submit_button" type="button" (click)="serverObj.addShout(displayName(), textArea.value)">Sumbit</button></p>
</form>
Data Interface:
shouts: [{
shoutUser: string;
shoutContent: string;
shoutTime: Date;
}];
Server Class (where shouts are being added to database):
public addShout(user: string, content: string): void{
//setting the proper time
function addZero(i) {
if (i < 10) {
i = "0" + i;
}
return i;
}
function nonMilitary(j){
return ((j + 11) % 12 + 1);
}
function amPM(k){
if(k >= 12){
return "PM";
}
else return "AM";
}
let date = new Date();
let hours = date.getHours();
let time = "(" + addZero(nonMilitary(hours)) + ":" +
addZero(date.getMinutes()) + " " + amPM(hours) + ")";
//TODO add id calling variable to hold the current day.
let day = ServerData.findOne({id: "Mark"});
ServerData.update(day._id, { $push: { shouts: { shoutUser: user,
shoutContent: content, shoutTime: time }}});
}
Chat OutPut:
(11:58 AM) PizzaLord : <img src="https://www.shabboshouse.org/wp-
content/uploads/2015/11/rocks2.jpg"/>
(12:03 PM) PizzaLord : shout
(12:08 PM) PizzaLord : Google.com website
Thanks for your help.
As explained in this article, interpolation causes the content to be escaped, and the HTML tags to be displayed as plain text.
Instead of using interpolation:
<span>: {{y.shoutContent}}</span>
you can set the innerHTML property with data binding, as shown in this stackblitz:
<span [innerHTML]="': ' + y.shoutContent"></span>

How to filter data by date in Angular js

I want to filter data by event date. I have the following options to filter: current day, current month and current year. Below you can see what I have so far:
function dateCtrl($scope) {
var d = new Date();
var curr_date = d.getDate();
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
$scope.dateToday = Date.parse(curr_month + "/" + curr_date + "/" + curr_year);
$scope.dateRange = "";
$scope.dataModels = [
{age:5,name:'John Lennon',eventDate:"1390524400000"},
{age:12,name:'Nick Young',eventDate:"1377500400000"},
{age:10,name:'Mike Johnson',eventDate:"1374044400000"},
{age:15,name:'Lisa Leslie',eventDate:"1335942000000"}
];
$scope.eventDateFilter = function(column) {
if(column === 'today') {
$scope.dateRange = $scope.dateToday;
} else if (column === 'currentWeek') {
//need logic
} else if (column === 'currnetMonth') {
//need logic
} else if (column === 'currnetYear') {
//need logic
}else {
$scope.dateRange = "";
}
}
}
and here I have the controller:
<div ng:controller="dateCtrl">
Date Filter
<ul class="inline">
<li><a href ng-click="eventDateFilter('all')">All</a></li>
<li><a href ng-click="eventDateFilter('today')">Today</a></li>
<li><a href ng-click="eventDateFilter('pastWeek')">Past Week</a></li>
<li><a href ng-click="eventDateFilter('pastMonth')">Past Month</a></li>
</ul>
<table class="table">
<tr>
<th>Name</th>
<th>Age</th>
<th>Event Date</th>
</tr>
<tr ng:repeat="data in dataModels | filter:dateRange">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
<td>{{data.eventDate | date:medium}}</td>
</tr>
</table>
</div>
I have the entire code here : The code
Original Answer
First, let me paraphrase your question (to make sure I answer to what you asked), as I'm not 100% sure about it:
I have a list of {age: <Number>, name: <String>, eventDate: <Timestamp>} objects and I want to filter them by their eventDate property. E.g. I want only objects with a eventDate in the current week.
To achieve this you have to minimally reorder your Controller:
$scope.dateRanges = {
all: {from: 0, to: Number.MAX_VALUE},
// defining getCurrent[Week,Month,Year]Range() left open for you,
// https://stackoverflow.com/questions/8381427/ is a good start
week: getCurrentWeekRange(),
month: getCurrentMonthRange(),
year: getCurrentYearRange(),
};
$scope.currentDateRange = $scope.dateRanges.all; // as initial value
$scope.eventDateFilter = function(event) {
return $scope.currentDateRange.from <= event.eventDate
&& event.eventDate <= $scope.currentDateRange.to;
});
Then you can use it in the template as
<ul>
<li ng-click="currentDateRange = dateRanges.all">show all</li>
<li ng-click="currentDateRange = dateRanges.week">show week</li>
<li ng-click="currentDateRange = dateRanges.month">show month</li>
<li ng-click="currentDateRange = dateRanges.year">show year</li>
</ul>
<table>
<tr ng-repeat="data in dataModels | filter:eventDateFilter">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
<td>{{data.eventDate | date:medium}}</td>
</tr>
</table>
The important difference is that you don't call functions on ng-clicking your navigation, but just change the model (and let angular update the view).
This is what we were used to do (from jQuery & the likes) for years. But with angular you need a mind shift. The template views the model and updates automatically once the model changes. You don't have to initiate those updates yourself.
Edit: getCurrentDayRange()
As the question arose in the comments, here's how you create a range (e.g. for the current day). It is heavily inspired by this answer to the question I cited above.
function getCurrentDayRange() {
// new Date() returns the moment it is called by default
return {
// the day starts at 00:00:00.000
from: new Date().setHours(0, 0, 0, 0),
// it ends at 23:59:59.999
to: new Date().setHours(23, 59, 59, 999)
};
}
On the question when to call eventDateFilter: it gets called by the AngularJS digest loop, you never call it yourself. See the Scope documentation for a deep-dive.
To simplify the calculation you could use moment.js
function getCurrentDayRange() {
return {
from: moment().startOf('day'),
to: moment().endOf('day')
};
}
function getCurrentWeekRange() {
return {
from: moment().startOf('week'),
to: moment().endOf('week')
};
};
function getCurrentMonthRange() {
return {
from: moment().startOf('month'),
to: moment().endOf('month')
};
}
function getCurrentYearRange() {
return {
from: moment().startOf('year'),
to: moment().endOf('year')
};
}

Categories

Resources