I have a pagination service and component which is working fine except that the dataSource is empty when loading the page first time. By second time, the dataSource is ready and I can display the dataTable and paginate.
Is there any fix to work around this issue, so that the function is invoked after the data is ready/loaded?
setTimeOut() would not be an option based on my tries.
list-user.service.ts:
let registeredUser:USers[] = [];
#Injectable()
export class ListUserService {
constructor(public loginService:LoginService){}
getUserDatasource(page:number, limit:number, sortBy?:string, sortType?:DatatableSortType): IPaginableUsers {
this.loginService.getUsersList().subscribe(
(res) => {
registeredUser = res.message
return registeredUser;
}, (err) => {})
}
...
...
}
list-users.component.ts:
export class ListUsersComponent implements AfterViewInit, OnDestroy {
...
...
constructor( private listUserService:ListUserService, private changeDetectorRef:ChangeDetectorRef) {
this.fetchUserDataSource();
}
ngOnInit(){}
ngAfterViewInit() {
if (this.datatable) {
Observable.from(this.datatable.selectionChange).takeUntil(this.unmount$).subscribe((e:IDatatableSelectionEvent) =>
this.currentSelection$.next(e.selectedValues)
);
Observable.from(this.datatable.sortChange).takeUntil(this.unmount$).subscribe((e:IDatatableSortEvent) =>
this.fetchUserDataSource(this.currentPagination.currentPage, this.currentPagination.itemsPerPage, e.sortBy, e.sortType)
);
Observable.from(this.pagination.paginationChange).takeUntil(this.unmount$).subscribe((e:IDatatablePaginationEvent) =>
this.fetchUserDataSource(e.page, e.itemsPerPage)
);
}
}
ngOnDestroy() {
this.unmount$.next();
this.unmount$.complete();
}
shuffleData() {
this.users$.next(shuffle(this.users$.getValue()));
this.currentSelection$.next([]);
this.changeDetectorRef.detectChanges();
}
private fetchUserDataSource(page:number = this.currentPagination.currentPage, limit:number = this.currentPagination.itemsPerPage, sortBy:string | undefined = this.currentSortBy, sortType:DatatableSortType = this.currentSortType) {
if (sortBy) {
this.currentSortBy = sortBy;
this.currentSortType = sortType;
}
const { users, pagination } = this.listUserService.getUserDatasource( page, limit, sortBy, sortType);
this.users$.next(users);
this.currentSelection$.next([]);
this.currentPagination = pagination;
}
}
you MUST subscribe in an ngOnInit to this.listUserService.getUserDataSource in your list-user-component
export class ListUsersComponent implements OnInit,...
{
page:number=1;
limit:number=5;
sortBy:string='';
....
ngOnInit() {
this.listUserService.getUserDataSource(page, limit, sortBy, sortType).subscribe((res)=>{
....here ...
}
}
Related
Hell, I have an e-commerce application in which I am trying to empty the shopping cart after payment is successful, no matter what I have tried the array will not empty. I have tried cartItems.length = 0,
cartItems = [] as well as splice. I must be missing something. Code snippet below I will walk you through it.
This is my Model
import { Product } from './product';
export class CartItem {
id: number;
productId: number;
productName: string;
qty: number;
price: number;
size:string;
imageUrl:string;
constructor(id:number, size:string,
product:Product, qty= 1) {
this.id = id;
this.productId = product.id;
this.price = product.price;
this.size = size;
this.productName = product.name;
this.qty = qty;
this.imageUrl = product.imageUrl;
}
}
This is my car service as you can see I remove and add to cart as well as get cart items, there is no problem here. The remove and add are button clicks on html
export class CartService {
product: any;
cartItem: CartItem[] = [];
cartUrl = 'http://localhost:4000/cart';
constructor(private http: HttpClient, private router: Router,
private route: ActivatedRoute) {
}
getCartItems(): Observable<CartItem[]> {
return this.http.get<CartItem[]>(cartUrl).pipe(
map((result: any[]) => {
let cartItems: CartItem[] =[];
for(let item of result) {
cartItems.push( new CartItem(item.id, item.size,
item.product, item.imageUrl ));
}
return cartItems;
})
);
}
addProductToCart(product:Product):Observable<any>{
return this.http.post(cartUrl, {product});
}
RemoveProductFromCart(id:number):Observable<void>{
//this.cartItems.splice(index,1)
alert("show deleted item");
return this.http.delete<CartItem[]>(`${this.cartUrl}/${id}`)
.pipe(catchError(_err => of (null))
);
}
buttonClick() {
const currentUrl = this.router.url;
this.router.navigateByUrl('/', {skipLocationChange:
true}).then(() => {
this.router.navigate([currentUrl]);
});
//alert("Should Reload");
}
addPurseToCart(product:Product):Observable<any>{
return this.http.post(cartUrl, {product})
}
}
Here is the checkout component, I injected the cart component so I could call the the empty cart function which resides in the cart component. I did import the CartComponent. Check out is based on cart. When the cart is emptied so should checkout
#Component({
providers:[CartComponent], //injected CartComponent
selector: 'app-checkout',
templateUrl: './checkout.component.html',
styleUrls: ['./checkout.component.scss']
})
export class CheckoutComponent implements OnInit {
// #ViewChild(CartComponent) // #ViewChild(CartItemComponent) cartitemComponent: CartItemComponent
cartComponent: CartComponent
#Input() product: CartItem;
#Input() cartItem: CartItem;
cartUrl = 'http://localhost:4000/cart';
size;
cartItems = [];
cartTotal = 0;
itemTotal = 0;
shipping = 8.00;
estimatedTax = 0;
myValue: any;
constructor(private msg: MessengerService, private route:
ActivatedRoute,
private router: Router, private
cartService:CartService,
private productService: ProductService, private
comp:CartComponent) {}
ngOnInit() {
this.loadCartItems();
}
}
loadCartItems(){
this.cartService.getCartItems().subscribe((items:
CartItem[]) => {
this.cartItems = items;
this.calcCartTotal();
this.calNumberOfItems();
})
}
calcCartTotal() {
this.cartTotal = 0;
this.cartItems.forEach(item => {
this.cartTotal += (item.qty * item.price);
})
this.cartTotal += this.shipping;
this.myValue = this.cartTotal
render(
{
id:"#paypal-button-container",
currency: "USD",
value: this.myValue,
onApprove: (details) =>{
alert("Transaction Suceessfull")
console.log(this.myValue);
this.comp.handleEmptyCart();
}
}
);
}
calNumberOfItems(){
console.log("Trying to get tolal items")
this.itemTotal = 0;
this.cartItems.forEach(item => {
this.itemTotal += item.qty;
})
}
}
cart component
export class CartComponent implements OnInit {
#Input() product: CartItem;
#Input() cartItem: CartItem;
//items: CartItem [] =[];
cartUrl = 'http://localhost:4000/cart';
val;
size;
cartItems = [];
cartTotal = 0
itemTotal = 0
constructor(private msg: MessengerService, private
cartService:CartService, private productService:
ProductService, private formBuilder:FormBuilder, private
_data:AppserviceService, private router:Router) { }
ngOnInit(): void {
this.handleSubscription();
this.loadCartItems();
}
handleSubscription(){
this.msg.getMsg().subscribe((product: Product) => {
})
}
loadCartItems(){
this.cartService.getCartItems().subscribe((items:
CartItem[]) => {
this.cartItems = items;
console.log("what is in cartItems" + this.cartItems)
console.log("What does this property hold" +
this.cartItem)
this.calcCartTotal();
this.calNumberOfItems();
})
}
calcCartTotal() {
this.cartTotal = 0
this.cartItems.forEach(item => {
this.cartTotal += (item.qty * item.price)
})
}
calNumberOfItems(){
console.log("Trying to get tolal items")
this.itemTotal = 0
this.cartItems.forEach(item => {
this.itemTotal += item.qty
})
}
handleEmptyCart(){
alert("Hit Empty Cart");
/*here I get the cart items to see what is in the array
and try to empty, it does show tow objects in the
array*/
this.cartService.getCartItems().subscribe((items:
CartItem[]) => {
this.cartItems = items;
this.cartItems.length=0
// this.cartItems = [];
console.log("what is in cartItems" + this.cartItems)
})
}
}
I have used different approaches trying to empty the cart nothing works. It makes me think I'm stepping on something or somehow creating events calling the loadCartItems to many times not sure but according to my research one of these approaches should work. If someone can please help me out I'm stuck. I would greatly appreciate it.
Short answer: run change detection ChangeDetectorRef.detectChanges()
Long answer:
You need to understand how angular works.
Lets assume simple example:
<div id="val">{{val}}</div><button (click)="val++">Increase<div>
So when u click button variable changes, but who changes actual #val div content? Answer is that Angular uses zone.js to patch/change a lot of functions to run Angular change detection after most of JS events (you can control type of this events to include/exclude some of them). Also Promise is patched same way, thats why after some Promise is resolve change detection is run.
However, here you run some render method with onApprove callback which is probably some 3rd party library (?) and zone.js is not aware of it (https://angular.io/guide/zone#ngzone-run-and-runoutsideofangular). And though running detectChanges gonna help u, u better re-write your code, so onApprove is always in Angular zone and u never gonna face similar bugs in future when using this method.
Here is my component code. In this code I have stored all data in a local array to find an item from this array. But when I try to get an element from this array it shows undefined.
//-------------------------------------------------------------
Component.ts
export class AccountsComponent implements OnInit
{
retVal = [];
constructor(
public service:AccountingService)
{
this.service.getAccounts().forEach(item=>{
this.retVal.push(item['chartofaccount']); // Locally stored the value to an array//
});
}
ngOnInit()
{
console.log(this.getAccountById(2));
}
getAccountById(id)
{
return this.retVal.find(x => x.id === id); // Return value showed undefined//
}
} //-------------------------------------------------------------
Service.ts
getAccounts():Observable<ChartOfAccount[]>
{
return this._htc.get<ChartOfAccount[]>(this.apiUrl+'chart-of-account', httpOptions)
.pipe(
tap(data => console.log("Data:", data)),
);
}
Try to call your service methods in new method in your component instead of constructor.
This approach should fix your problem.
Why?
Angular: function calling on constructor
https://www.angularjswiki.com/angular/what-is-the-difference-between-constructor-and-ngoninit-in-angular/
//-------------------------------------------------------------
Component.ts
export class AccountsComponent implements OnInit
{
retVal = [];
constructor(
public service:AccountingService)
{
});
}
ngOnInit()
{ this.getAccountsData();
console.log(this.getAccountById(2));
}
getAccountsData() {
this.service.getAccounts().forEach(item=>{
this.retVal.push(item['chartofaccount']); // Locally stored the value to an array//
});
}
getAccountById(id)
{
return this.retVal.find(x => x.id === id); // Return value showed undefined//
}
} //-------------------------------------------------------------
I have a list of services that have multiple property like serviceId, serviceName and photoProfile called from a database using a spring REST API.
The 'photoProfile' property only has the id of the profile picture which if you use the 'localhost:8082/downloadFile/'+photoProfile would get you the image which is in turn is stored in a folder in the spring project.
After looking for a while online, I've found how I can actually get the real image to display on my website but now I'm stuck since I need to do this for the whole list.
Here's my angular code:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { LoginComponent } from '../login/login.component';
import { UserService } from '../user.service';
import { Observable, forkJoin } from 'rxjs';
import { HttpHeaders, HttpClient } from '#angular/common/http';
import { combineLatest } from 'rxjs/operators';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
loggedIn: boolean;
services: any[] = [];
imgSrc: any;
newList: any[] = [];
constructor(private router: Router, private service: UserService, private http: HttpClient) {
}
ngOnInit() {
this.service.getServices().subscribe(res => {
this.services = res;
console.log('services: ', this.services);
});
for (let i = 0; i < this.services.length; i++) {
const element = this.services[i];
this.getImage('http://localhost:4200/downloadFile/' + element.photoProfile).subscribe(data => {
this.createImageFromBlob(data);
});
this.newList.push(this.imgSrc);
console.log(this.newList);
//I want to add the element from the services list and the image value after being converted to the new list
}
}
getImage(imageUrl: string): Observable<Blob> {
return this.http.get(imageUrl, {responseType: 'blob'});
}
createImageFromBlob(image: Blob) {
const reader = new FileReader();
reader.addEventListener('load', () => {
this.imgSrc = reader.result;
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
}
Thank you for your help.
You need to add the new list inside the ngOnInit after you are subscribing to the services list. Because currently. You don't have the services when the for loop runs. You need to run the for loop after you have the result from services. Like this:
ngOnInit() {
this.service.getServices().subscribe(res => {
this.services = res;
console.log('services: ', this.services);
for (let i = 0; i < this.services.length; i++) {
const element = this.services[i];
this.getImage('http://localhost:4200/downloadFile/' + element.photoProfile).subscribe(data => {
this.createImageFromBlob(data);
element.imgSrc = this.imgSrc;
this.newList.push(element);
});
console.log(this.newList);
}
}
});
I had similar situation, and method of Muhammad Kamran work particularry for me, because images loaded into array absolutely randomly. As i understand, the speed of FOR cycle is faster than picture download speed. The solution is - pushing into array in createImageFromBlob (in case of i'mgnome). In my case it was like this:
export interface File {
...
original_name: name of file;
linkToPicture: string;
image: any;
}
...
files: File[] = [];//array for incoming data with link to pictures
this.getTable(...)
...
getTable(sendingQueryForm: QueryForm){
this.queryFormService.getTable(sendingQueryForm)
.subscribe(
(data) => {
this.files=data as File[];
for (let i = 0; i < this.files.length; i++) {
this.getImage('/api/auth/getFileImage/' + this.files[i].linkToPicture).subscribe(blob => {
this.createImageFromBlob(blob,i);
});
}
},
error => console.log(error)
);
}
getImage(imageUrl: string): Observable<Blob> {
return this.http.get(imageUrl, {responseType: 'blob'});
}
createImageFromBlob(image: Blob, index:number) {
const reader = new FileReader();
reader.addEventListener('load', () => {
this.files[index].image = reader.result;
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
and in HTML:
<div *ngFor="let block of files; let i = index" >
<mat-card class="grid-card">
<div>
<img [src]="block.image" width=120>
<p>{{block.original_name}}</p>
</div>
</mat-card>
</div>
I hope it will useful for someone and thanks for topic!
I am confused with my codes in Angular 2. In my ts file I have:
import { Test } from '../../../../models/test';
import { TestService } from '../../../../services/test.service';
import { Job} from '../../../../models/job';
import { JobService } from '../../../../services/job.service';
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
job: Job;
constructor(
private testService: TestService,
private jobService: JobService
) { }
ngOnInit() {
this.getTest();
this.getJob(this.test.id);
}
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
}
}
);
}
getJob(id: number){
this.jobService.getJobByTestId(id).subscribe();
}
}
And in my HTML file I have:
<h3 class="box-title">{{ test?.title }} </h3>
Surely, the data binding {{ test?.title }} is working and showing the data. But during the call of another function getJob(this.test.id) on my ts file, it says an undefined parameter.
How this became undefined when it is showing perfectly in the view? I wanted to use the data inside this.test variable to other functions but I cannot since it is undefined.
Please someone has the same issue with me and how did you fix this problem. Thank you.
This is because the console.log() is executed before this.test gets a value assigned. The execution is async, which means it scheduled to run later, while the execution of the sync code continues immediately.
{{ test?.title }} is undefined first but updated later, but the change happens too fast for a human too recognize.
If you move the console.log() you'll see the value
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
console.log(this.test);
}
}
);
}
update
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
job: Job;
constructor(
private testService: TestService,
private jobService: JobService
) { }
ngOnInit() {
this.getTest().subscribe(val =>
this.getJob(this.test.id));
}
getTest(){
// add `return` and replace `subscribe` by `map`
return this.testService.getById(40).map(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
}
}
);
}
getJob(id: number){
this.jobService.getJobByTestId(id).subscribe();
}
}
Replace you code with this :
import { Test } from '../../../../models/test';
import { TestService } from '../../../../services/test.service';
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
constructor(
private testService: TestService
) { }
ngOnInit() {
this.getTest();
// console.log(this.test);
}
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
console.log(this.test);
this.getJob();
}
}
);
}
getJob(){
this.jobService.getJobByTestId(this.test.id).subscribe();
}
}
You have just put the console.log() at wrong place.
As this.testService.getById(40).subscribe is async part , so
ngOnInit() {
this.getTest();
// at this time we'll not have this.test ;
// console.log(this.test);
}
I need to generate sanitized css property to use with my component template to set the background image of the div:
<div *ngFor="let Item of Items"
[style.background-image]="Item.imageStyle
(click)="gotoDetail(Item.iditems)">
</div>
using data obtained through a data service. The component is:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { DomSanitizer } from '#angular/platform-browser';
import { OnInit } from '#angular/core';
import { Item } from '../models/Item';
import { CollectionDataService } from '../services/CollectionData.service';
#Component({
selector: 'mainpage',
templateUrl: 'app/mainpage/mainpage.component.html',
styleUrls: ['app/mainpage/mainpage.component.css']
})
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.collectionDataService.getItems().subscribe(
Items => this.Items = Items
);
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
constructor(
private router: Router,
private sanitizer: DomSanitizer,
private collectionDataService: CollectionDataService
) {
}
gotoDetail($iditems: number): void {
this.router.navigate(['/viewer', $iditems]);
}
}
But it doesn't work because the statement that generates the sanitized property
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
doesn't find the loaded data. The error that I'm seeing in the browser console is:
core.umd.js:3070 EXCEPTION: Uncaught (in promise): Error: Error in ./MainpageComponent class MainpageComponent_Host - inline template:0:0 caused by: Cannot read property 'map' of undefined
TypeError: Cannot read property 'map' of undefined
The data service is:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Item } from '../models/Item';
import { DomSanitizer } from '#angular/platform-browser';
#Injectable()
export class CollectionDataService {
constructor(
private http: Http,
private sanitizer: DomSanitizer
) { }
getItems() {
return this.http.get('app/mocksdata/items.json').map(
response => <Item[]>response.json().items
)
}
}
And the provided items.json:
{
"items": [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
}
If I set static data in the component, instead of using the data service, everything works:
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.Items = [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
How can I force the sanitizer statement to wait that the async data are fully loaded? Alternatively how can I generate sanitized properties directly in the service?
EDIT
The best answer comes from PatrickJane below:
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}
I also solved this problem working directly in the service method (credits), but it is more verbose:
return this.http.get('app/mocksdata/items.json')
.map( (responseData) => {
return responseData.json().items;
})
.map(
(iitems: Array<any>) => {
let result:Array<Item> = [];
if (iitems) {
iitems.forEach((iitem) => {
iitem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+iitem.iditems+".jpg)");
result.push(<Item>iitem);
});
}
return result;
}
)
The subscribe function is async so your map function called before the subscribe function run. So in this phase the array is undefined because you doesn't set any initial value.
The solution is to do this inside the subscribe function and to initialize the Items with empty array.
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}