angular5:ngFor works only in second button click - javascript

My scenario as follows
1) When the user enters a keyword in a text field and clicks on the search icon it will initiate an HTTP request to get the data.
2)Data is rendered in HTML with ngFor
The problem is on the first click the data is not rendered in HTML but I am getting the HTTP response properly, and the data rendered only on second click.
component.ts
export class CommerceComponent implements OnInit {
private dealList = [];
//trigger on search icon click
startSearch(){
//http service call
this.getDeals();
}
getDeals(){
this.gatewayService.searchDeals(this.searchParams).subscribe(
(data:any)=>{
this.dealList = data.result;
console.log("Deal list",this.dealList);
},
(error)=>{
console.log("Error getting deal list",error);
this.dealList = [];
alert('No deals found');
}
);
}
}
Service.ts
searchDeals(data){
var fd = new FormData();
fd.append('token',this.cookieService.get('token'));
fd.append('search',data.keyword);
return this.http.post(config.url+'hyperledger/queryByParams',fd);
}
HTML
//this list render only on second click
<div class="deal1" *ngFor="let deal of dealList">
{{deal}}
</div>
UPDATE
click bind html code
<div class="search-input">
<input type="text" [(ngModel)]="searchParams.keyword" class="search" placeholder="" autofocus>
<div class="search-icon" (click)="startSearch()">
<img src="assets/images/search.png">
</div>
</div>

According to Angular official tutorial, you could have problems if you bind a private property to a template:
Angular only binds to public component properties.
Probably, setting the property dealList to public will solve the problem.

Remove "private" from your dealList variable. That declaration makes your component variable available only during compile time.
Another problem: you are implementing OnInit in yout component but you are not using ngOnInit. Angular is suposed to throw an error in this situation.

My suggestion is to switch to observable:
I marked my changes with CHANGE
component.ts
// CHANGE
import { Observable } from 'rxjs/Observable';
// MISSING IMPORT
import { of } from 'rxjs/observable/of';
export class CommerceComponent implements OnInit {
// CHANGE
private dealList: Observable<any[]>; // you should replace any with your object type, eg. string, User or whatever
//trigger on search icon click
startSearch() {
//http service call
this.getDeals();
}
getDeals() {
this.gatewayService.searchDeals(this.searchParams).subscribe(
(data:any)=>{
// CHANGE
this.dealList = of(data.result);
console.log("Deal list",this.dealList);
},
(error)=>{
console.log("Error getting deal list",error);
// CHANGE
this.dealList = of([]);
alert('No deals found');
}
);
}
}
HTML
<!-- CHANGE -->
<div class="deal1" *ngFor="let (deal | async) of dealList">
{{deal}}
</div>

Try this:
this.dealList = Object.assign({},data.result);
Better do this inside the service.
By default, the angular engine renders the view only when it recognizes a change in data.

Related

ngFor don`t show all items immediately after place_changed event from google place autocomplete

I need to add result to the list after place_changed event. I display the list below the input in which I find locations. Event works and result is pushed to array items. But the problem is that new added item don`t display immediately. It displayed after some time or when I click on form where this input is displayed.
.ts:
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
items = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
this.items.push(this.input.nativeElement.value);
this.input.nativeElement.value = '';
}
}
.html:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<div *ngFor="let item of items; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
Thanks for the help!
Probably your component Change Detection Strategy is OnPush or google autocomplete is running outside zone.js:
changeDetection: ChangeDetectionStrategy.OnPush
And since items is array and it is stored in memory by reference you need to run manually change detenction:
constructor(private cdr: ChangeDetectorRef)
public addToListSelectedItem() {
...
this.input.nativeElement.value = '';
this.cdr.detectChanges();
Even better would be to work with an RxJS Subject, an Observable for items$ and use the async pipe. The async pipe works like magic what concerns updating the template :-)!
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
itemsSubject$ = new Subject<any[]>();
items$ = this.itemsSubject$.asObservable();
// Use a separate array to hold the items locally:
existing = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
// Use spread syntax to create a new array with the input value pushed at the end:
this.existing = [...this.existing, this.input.nativeElement.value];
// Send the newly created array to the Subject (this will update the items$ Observable since it is derived from this Subject):
this.itemsSubject$.next(this.existing);
this.input.nativeElement.value = '';
}
}
// I added the deleteTask implementation to show you how this works with the subject:
deleteTask(index: number) {
// The Array "filter" function creates a new array; here it filters out the index that is equally to the given one:
this.existing = this.existing.filter((x, i) => i !== index);
this.itemsSubject$.next(this.existing);
}
And in the template:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<!-- Only difference here is adding the async pipe and using the items$ Observable instead -->
<div *ngFor="let item of items$ | async; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
For a working example of the RxJS Subject in this concept, see https://stackblitz.com/edit/angular-ivy-dxfsoq?file=src%2Fapp%2Fapp.component.ts.
Maybe beyond this question, but since you're using a reactive form, why not use this.locationForm.get('locality').setValue('...') to set the input instead of using a ViewChild for working with the input? More control this way then using the ViewChild.

Angular modal not updates with component variable change

I am using a component for sending SMS and it is added to the nagigation bar component like this :
<ng-template #smsModal let-c="close" let-d="dismiss">
<div class="modal">
<app-sms></app-sms>
</div>
</ng-template>
The sms component HTML looks likes the following :
<form>
<input type="text" [(ngModel)]="send.mobileNumber" #ctrl="ngModel" name="mobileNumber">
<button class="send-SMS-btn ripple" (click)="sendMessage()" [disabled]="textSending">
<span *ngIf="!textSending">Send Message</span>
<app-spinner *ngIf="textSending"></app-spinner>
</button>
<div class="textmsg text-danger" *ngIf="textError">{{textError}}</div>
<div class="textmsg success" *ngIf="textSuccess">{{textSuccess}}</div>
</form>
the method sendMessage() has the following code :
this.textSending = false;
if (_.isEmpty(this.send.mobileNumber)) {
this.textError = "Please enter a valid phone number";
return false;
}
this.textSending = true;
this.textSuccess = null;
// API call and stuff
}
When I console the this.textError, it is giving the correct error message, but this is not updated in view. The error container div itself is not populated and also the spinner is not showing. Somehow, the view is not detecting changes . The API call is triggered, but it also not showing error message, even if it is showing in console. How this can be fixed ?
Probably need to restart the digest cycle again. use the changeDetectorRef service
constructor(
private changeDetectorRef: ChangeDetectorRef,
) {
}
call the detectChanges method inside sendMessage method
if (_.isEmpty(this.send.mobileNumber)) {
this.textError = "Please enter a valid phone number";
return false;
}
this.textSending = true;
this.textSuccess = null;
this.changeDetectorRef.detectChanges(); // start the cycle again
A typical way for me to address input data from passed into a component is to declare the input as a BehaviorSubject.
For example:
import { Component, Input } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs/Rx';
#Component({
selector: 'my-component-selector',
templateUrl: './my.component.html'
})
export class MyComponent {
#Input()
public set yourErrorTextMessage(data) {
this.yourErrorTextMessageSubject.next(data);
};
private yourErrorTextMessageSubject = new BehaviorSubject<string>("");
}
What this does is tie the actual value to the behavior subject, and the template gets notified of any changes to it, including values prior to the template initializing. Thus your error message can be updated asynchronously and your component will get the value for the last error message whether it happened before or after the initialization.

what is the equevalant for getelementbyid in angular 2 [duplicate]

I have a code:
document.getElementById('loginInput').value = '123';
But while compiling the code I receive following error:
Property value does not exist on type HTMLElement.
I have declared a var: value: string;.
How can I avoid this error?
Thank you.
if you want to set value than you can do the same in some function on click or on some event fire.
also you can get value using ViewChild using local variable like this
<input type='text' id='loginInput' #abc/>
and get value like this
this.abc.nativeElement.value
here is working example
Update
okay got it , you have to use ngAfterViewInit method of angualr2 for the same like this
ngAfterViewInit(){
document.getElementById('loginInput').value = '123344565';
}
ngAfterViewInit will not throw any error because it will render after template loading
(<HTMLInputElement>document.getElementById('loginInput')).value = '123';
Angular cannot take HTML elements directly thereby you need to specify the element type by binding the above generic to it.
UPDATE::
This can also be done using ViewChild with #localvariable as shown here, as mentioned in here
<textarea #someVar id="tasknote"
name="tasknote"
[(ngModel)]="taskNote"
placeholder="{{ notePlaceholder }}"
style="background-color: pink"
(blur)="updateNote() ; noteEditMode = false " (click)="noteEditMode = false"> {{ todo.note }}
</textarea>
import {ElementRef,Renderer2} from '#angular/core';
#ViewChild('someVar') el:ElementRef;
constructor(private rd: Renderer2) {}
ngAfterViewInit() {
console.log(this.rd);
this.el.nativeElement.focus(); //<<<=====same as oldest way
}
A different approach, i.e: You could just do it 'the Angular way' and use ngModel and skip document.getElementById('loginInput').value = '123'; altogether. Instead:
<input type="text" [(ngModel)]="username"/>
<input type="text" [(ngModel)]="password"/>
and in your component you give these values:
username: 'whatever'
password: 'whatever'
this will preset the username and password upon navigating to page.
Complate Angular Way ( Set/Get value by Id ):
// In Html tag
<button (click) ="setValue()">Set Value</button>
<input type="text" #userNameId />
// In component .ts File
export class testUserClass {
#ViewChild('userNameId') userNameId: ElementRef;
ngAfterViewInit(){
console.log(this.userNameId.nativeElement.value );
}
setValue(){
this.userNameId.nativeElement.value = "Sample user Name";
}
}

Angular 5 w/Angular Material - Extracting an object property from ngFor to use in component

<div *ngFor="let player of players">
<h4 mat-line>{{player.firstName}} {{player.lastName}} - {{player.id}}</h4>
</div>
I'm doing a HTTP get call from my player.service.ts file, and then looping through the player object that gets returned, printing out the firstName, lastName and id properties in a massive player list.
I need to extract a specific player ID at a given point in the loop so that I can pass that down to a child Edit Player component that opens a modal with that specific player's information pre-filled in the form (using NgModel and a getbyId call to the API to get the player object). How would I go about doing this?
It looks like you're using #angular/material. If so, you should be able to use a click handler that loads the player data and opens up a dialog with their provided dialog service.
eg:
Template:
<div *ngFor="let player of players">
<h4 (click)="handlePlayerClick(player.id)"
mat-line>
{{player.firstName}} {{player.lastName}} - {{player.id}}
</h4>
</div>
Component:
constructor(private dialogService: MatDialog, private playerApi: PlayerApiService) { }
handlePlayerClick(playerId: string): void {
// potentially open a MatDialog here
this.playerApi.getById(playerId).subscribe((playerData: PlayerInterface) => {
const dialogConfig = {
data: {
playerData: playerData
}
} as MatDialogConfig;
this.dialogService.open(EditPlayerComponent, dialogConfig);
});
}
Documentation: https://material.angular.io/components/dialog/api
You'd want your child component to have a property like #Input() playerId: any; and then simply pass it in square brackets into the child tag like so:
<div *ngFor="let player of players">
<h4 mat-line>{{player.firstName}} {{player.lastName}} - {{player.id}}</h4>
<edit-player [playerId]="player.id"></edit-player>
</div>

Getting Date Formatted and Printed to Screen in Angular 2 App

I am trying to get the date pipe I'm using in my Angular app to parse out correctly when using it in the template within an input. Initially, before formatting the date, the code looked like this:
<input class="app-input" [readonly]="!hasAdminAccess"
[(ngModel)]="staff.profile.hireDate" placeholder="None"
[field-status]="getPropertyStatus('profile.hireDate')"/>
The closest I've gotten with the date pipe is this:
<input class="app-input"
{{ staff.profile.hireDate | date:'shortDate' }} placeholder="None"
[field-status]="getPropertyStatus('profile.hireDate')"/>
But what that prints to the view is this (literally this):
> <input class="app-input" 3/18/2014 placeholder="None"
> [field-status]="getPropertyStatus('profile.hireDate')"/>
Now, you'll notice that the correctly formatted date is there (and the date transformation is happening successfully, to make it this:
3/18/2014
However, I don't want the rest (obviously). How can I rework the syntax here so as to get just the date to print? I've stared at it and tried a few tweaks, but as of yet haven't been able to get it to work.
You can use the get and set functions in typescript and ngModelChanged property to modify the ngModel after it has been set.
Component Template :
<input class="app-input" [(ngModel)]="hireDate" (ngModelChange)="dateChanged($event)"/>
Component Class :
import { Component } from '#angular/core';
import { DatePipe } from '#angular/common';
#Component({
selector: 'my-app',
template: `
<div>
<button (click)="setDate()">Set Date</button>
<input class="app-input" readonly="true" [(ngModel)]="hireDate" (ngModelChange)="dateChanged($event)" placeholder="None"/>
</div>
`,
})
export class App {
name:string;
staff: any;
myDate: any;
private _hireDate;
dateChanged(value) {
this.hireDate = this.datePipe.transform(value, 'shortDate');
}
set hireDate(value) {
this._hireDate = this.datePipe.transform(value, 'shortDate');
}
get hireDate() {
return this._hireDate;
}
setDate() {
this.hireDate = '10-03-1993';
}
constructor(private datePipe: DatePipe) {
}
}
The value of the input will be set whenever the input changes, so it might cause a UX issue, as the user would not be able to enter his prefered date. A workaround would be to call the date changed function whenever the user has entered his date. (For eg. via a button click).
I believe the set and get functions work only for class variables, in your case you have a object property. Modifying the set function as shown below would work.
set hireDate(value) {
this._hireDate = this.datePipe.transform(value, 'shortDate');
this.staff.profile.hireDate = this._hireDate;
}
I have also added a plunkr here.

Categories

Resources