Array of object showing undefined duing ngOninit in angular 5 - javascript

Even when I am trying to access inside html like this, its also not giving any value
<mat-nav-list>
<a mat-list-item href="" *ngFor="let link of products"> {{ link.name }} </a>
</mat-nav-list>
Here is my JSON
[{"id":215,"name":"Book ambulance","slug":"book-ambulance","permalink":"http://staging.drmedapp.com.cp-36.webhostbox.net/demo/product/book-ambulance/","date_created":"2018-02-24T08:31:01","date_modified":"2018-02-24T08:32:13","type":"simple","status":"publish","featured":false,"catalog_visibility":"visible","description":"","short_description":"","sku":"","price":"0","regular_price":"0","sale_price":"","date_on_sale_from":"","date_on_sale_to":"","price_html":"<span class=\"woocommerce-Price-amount amount\"><span class=\"woocommerce-Price-currencySymbol\">₹</span>0.00</span>","on_sale":false,"purchasable":true,"total_sales":0,"virtual":false,"downloadable":false,"downloads":[],"download_limit":-1,"download_expiry":-1,"download_type":"standard","external_url":"","button_text":"","tax_status":"taxable","tax_class":"","manage_stock":false,"stock_quantity":null,"in_stock":true,"backorders":"no","backorders_allowed":false,"backordered":false,"sold_individually":false,"weight":"","dimen
Here is my .ts file, I updated the code but still getting error this.products is not defined.
import { Component, OnInit } from '#angular/core';
import {Headers, Http, RequestOptions, URLSearchParams} from '#angular/http';
import 'rxjs/add/operator/toPromise';
import * as WC from 'woocommerce-api';
import { WooApiService } from 'ng2woo';
import * as CryptoJS from 'crypto-js';
#Component({
selector: 'app-pcat',
templateUrl: './pcat.component.html',
styleUrls: ['./pcat.component.scss']
})
export class PcatComponent implements OnInit {
WooCommerce: any;
public products: any;
data: any;
public crypto: any;
typesOfShoes = ['Boots', 'Clogs', 'Loafers', 'Moccasins', 'Sneakers'];
constructor(private woo: WooApiService) {}
public getALL(){
this.woo.fetchItems('products')
.then(products => {
for(var i = 0; i < products.length; i++) {
this.products.push(products[i])
}
})
.catch(error => console.log(error));
}
ngOnInit(): void {
this.getALL();
}
}

I think you're the problem is your not assigning the returned value of getAll() function to your products array.
public getALL(){
this.woo.fetchItems('products')
.then(products => {
for(var i = 0; i < products.length; i++) {
this.products.push(products[i])
}
})
.catch(error => console.log(error));
}

Looks the Products are loaded dynamically, when you make a API call. until API call finishes, the property products would be holding instillation value which is undefined for what you have done like below.
public products: any;
till products are loaded the template does not know this is an Array, that is where the template fails. now to fix initialize the property with empty array.
public products: any[] = [];
In short, looks this this is the problem of initialization.
Hope this helps

You need to populate your products property to the current response
public getALL(){
this.woo.fetchItems('products')
.then(products => { this.products = products; })
.catch(error => console.log(error));
}

Related

Angular removing elements from a list shared with a service

this problem is driving me crazy.
I have an array defined within a service, which is used in 3 other components:
This is the service, file products.service.ts (notice the product array of Products)
import { Injectable } from '#angular/core';
import { ​​HttpClient } from '#angular/common/http';
import { Product } from './../models/Product';
import { ProductForm, productFormToProduct } from './../models/ProductForm';
// #Injectable({
// providedIn: 'root'
// })
const apiUrl = 'http://localhost:3000/products';
#Injectable()
export class ProductsService {
public products: Product[] = [];
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get(apiUrl)
}
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
return this.http.delete(apiUrl + "/" + p.id)
}
storeNewProduct(pf: ProductForm) {
const idList = this.products.map((x) => {return x.id});
const i = Math.max(...idList) + 1;
const p = productFormToProduct(pf);
p.id = i;
this.products.push(p);
return this.http.post(apiUrl, p)
}
}
This is the component where i subscribe to getProducts, and fill the array (file products.component.ts):
import { Component, OnInit } from '#angular/core';
import { ProductsService } from '../../shared/services/products.service';
import { Product } from '../../shared/models/Product';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
}
}
And this is the component where i subscribe to deleteProduct (file product-card.component.ts):
import { Component, Input, OnInit } from '#angular/core';
import { ProductsService } from '../../services/products.service';
import { Product } from './../../models/Product';
#Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent implements OnInit {
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
}
#Input() product: Product
public buttonDeleteFunction() {
this.productsService.deleteProduct(this.product).subscribe();
}
}
The problem is, when i click on some delete product button, i have this weird behaviour:
Before click:
After click:
Here is the products.component.html file:
<div class="products__header">
<h3 class="products__heading">
Listado de productos ({{ products.length }})
</h3>
<input
class="products__search"
placeholder="Buscador"
type="search"
[(ngModel)]="searchText"
/>
</div>
<p *ngFor="let p of products">{{ p.name }}</p>
<p>{{ products }}</p>
<div class="products__list">
<app-product-card
*ngFor="let p of products | filterNames: searchText"
[product]="p"
></app-product-card>
</div>
Why do i get the expected behaviour in only two of the four places where i use the products list?
I know i can use an Output to manually remove the item from the list when i click the button, but i have been told that services are used instead of Inputs/Outputs when i want to share between multiple components, so i'd rather not use an Output for this
When you use your approach with common data on service layer then a common pitfall is that Angular does not detect the changes that affect your component. In that case you must inform your component for those changes using an emmiter.
Use an emmiter on service
productUpdated :EventEmitter = new EventEmitter();
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
this.productUpdated.emit(this.products);
return this.http.delete(apiUrl + "/" + p.id)
}
And then listen for that change ProductsComponent
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
this.productsService.productUpdated.subscribe( (data) => {
this.products = data;
});
}

Have realtime updates for a single Firestore document

There is a lot of documentation and examples of firestore collections getting realtime updates. However, there is very little for those who wish to have a single document have real time updates. I want to have a single document (an item), on a page where only the item will be viewed and manipulated and any changes to document, will have realtime updating.
Here is my component that wants to do stuff with the item:
import { Component, OnInit } from '#angular/core';
import { ItemsService } from '../shared/items.service';
import { ActivatedRoute, Router } from '#angular/router';
#Component({
selector: 'app-view-item',
templateUrl: './view-item.component.html',
styleUrls: ['./view-item.component.css']
})
export class ViewItem implements OnInit {
item;
private sub: any;
constructor(
// Service used for Firebase calls
private itemsService: ItemsService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
// Item retrieved from */item/:id url
this.sub = this.route.params.subscribe(params => {
this.getItem(params['id']);
});
}
getItem = (id) => {
this.itemsService.getItem(id).subscribe(res => {
console.log(res);
this.item = res;
console.log(this.item);
});
}
And the service it uses for calls:
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
#Injectable({
providedIn: 'root'
})
export class ItemsService {
constructor(
private firestore: AngularFirestore
)
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges();
}
}
The log I get for console.log(this.item) is undefined. Calling this.item in the console returns the same. I am unsure of how to proceed and would appreciate any guidance. Logging res in the console returns a byzantine object. Perhaps that's how I access the item, but if so, why is it not saved in this.item and how do I access the item's values?
snapshotChanges returns an observable of actions, not the actual value.
You should extract the value with action.payload.doc.data():
So your code should look like the following example.
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges()
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
})
);
}
Or you can use valueChanges of doc.
getItem(id) {
return this.firestore.collection('items').doc(id).valueChanges();
}

Variable data not retained to bind in HTML even after assigning data to it inside a subscribe() function

I have below app-product.ts code written as below:
#Component({
selector: 'app-product',
template: `
<div>
<h2>{{products.prod_name | uppercase}} Details</h2>
<div><span>Description: </span>{{products.prod_desc}}</div>
</div>`
})
export class ProductComponent {
products:any = [];
constructor(public rest: RestService){ this.getProducts() }
getProducts() {
this.products = [];
this.rest.getProducts().subscribe((data: {}) => {
console.log(data); // data is printing in console
this.products = data; // tried keeping debugger here
});
}
In the above code I am able to print the data in console but the variable products is not accessible in my template.
I also tried to keep a debugger at the point and I tried to print the products but it was showing as undefined every time.
Below is my angular service file for consuming REST API using Http Client as below:
#Injectable({
providedIn: 'root'
})
export class CarApiService {
getProducts(): Observable<any> {
return this.http.get(endpoint + 'products').pipe(
map(this.extractData));
}
}
I also tried to analyse if there is any callback issues in the code but could not figure what is the real cause of this problem.
I also tried looking into few threads like:
component variables inside a RxJS subscribe() function are undefined
but could not find any help. Can anyone please help me out?
Since the service was in other module i had to create single sharing tree for access that return values.
So the code must be placed inside the export shared module block as below:
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule
};
}
Your are trying to use product in template (*ngIf="product") while the data is inside products variable.
and, you should call getProducts() function to fetch the values inside your array variable.
Import OnInit Interface and call the fucntion:
export class ProductComponent implements OnInit {
products:any = [];
constructor(public rest: RestService){}
ngOnInit() {
this.getProducts();
}
getProducts() {
this.products = [];
this.rest.getProducts().subscribe((data: {}) => {
console.log(data); // data is printing in console
this.products = data; // tried keeping debugger here
});
}
update due comment
if you want show array in your template you have to iterate over the array:
template: `
<div *ngFor='let product of products'>
<h2>{{product.prod_name | uppercase}} Details</h2>
<div><span>Description: </span>{{product.prod_desc}}</div>
</div>`
#Component({
selector: 'app-product',
template: `
<div *ngFor='let product of products'>
<h2>{{product.prod_name | uppercase}} Details</h2>
<div><span>Description: </span>{{product.prod_desc}}</div>
</div>`
})
export class ProductComponent {
products:{}[] = [];
constructor(public rest: RestService){ this.getProducts() }
getProducts() {
this.products = [];
this.rest.getProducts().subscribe((data: {}[]) => {
this.products = data;
console.log(this.products); // this should not be undifind
});
}
otherwise check your spelling

Passing/binding data from app component to other components in Angular 2

EDIT: Comment by OP:
"Sorry , but I think I had had slight typo in enviroment/environment, sorry for wasting your time ,it seems to work now"
I have having trouble passing data from app components to child component in angular 2 . I recently started toying with angular 2 and trying to understand how it works. I tried to used the concept shown in this tutorial to do pass data to child component
https://angular.io/docs/ts/latest/tutorial/toh-pt3.html
But I think I am missing something
Here is my project: App component:
import { Component, ViewChild } from '#angular/core';
import { WorkflowService } from './components/workflow_display/workflow.service';
import { WorkflowDisplayComponent } from './components/workflow_display/workflow-display.component';
import { PropertyService } from './shared/property.service';
import '../../public/css/styles.css';
#Component({
selector: 'my-app',
template: require('./app.component.html')
})
export class AppComponent {
title = 'Hello World';
#ViewChild("taskDisplay") workflowDisplay: WorkflowDisplayComponent;
myEnvironment: String; //the variable I am trying to bind from
errorMessage: String;
workbenchBaseUrl : String = 'workbenchBaseUrl';
public selectedNavID : String = 'workspace_control_workStreamView';
public isWorkOrdersCollapsed = false;
public isWorkStreamsCollapsed = false;
constructor(private _propertyService : PropertyService){
}
ngOnInit(): void {
this._propertyService.getValue(this.workbenchBaseUrl)
.subscribe(environment => this.myEnvironment = environment,
error => this.errorMessage = <any>error);
}
}
app.component.html
<div>
<div>
<div>
<!--some html-->
<main class="col-sm-9 offset-sm-3 col-md-10 offset-md-2 pt-3 mh-100">
<workflow-display [environment] ="myEnvironment" #taskDisplay></workflow-display>
</main>
</div>
</div>
</div>
WorkDisplay component
import { Component, Input} from '#angular/core';
import { OnInit } from '#angular/core';
import { IGrcTask } from './grc-task';
import { WorkflowService } from './workflow.service';
import { PropertyService } from '../../shared/property.service';
#Component({
selector: 'workflow-display',
template: require('./workflow-display.component.html')
})
export class WorkflowDisplayComponent implements OnInit {
taskMode: string = 'workstream'; // 'workorder' or 'workstream' to currently identify the columns to display
taskQuery: string = 'process=workstream&taskStatus=RUNNING'; // the query parameters to pass to the tasks web service
workbenchUrl: string = 'http://localhost:8081'; // workbench URL
workbenchTaskPage: string = 'wsIndex'; // workbench page to use to open tasks
infoMessage: string;
errorMessage: string;
tasks: IGrcTask[];
currentTask: IGrcTask;
#Input()
environment: String; //the variable I am trying to bind to
workbenchBaseUrl : String = 'workbenchBaseUrl';
constructor() {
}
//called when user clicks a row
openTask(event: any, task: any) {
// this.environment is still undefined
window.open(this.environment + this.workbenchTaskPage + "?taskId=" + task.taskId + "&activitiWorkflow=true");
}
}
WorkDisplay.component.html
<--!some html-->
<tbody *ngIf='(taskMode == "workorder") && tasks && tasks.length'>
<ng-container *ngFor='let task of tasks; let i=index'>
<tr (click)="setCurrentTask($event, task)" (dblclick)="openTask($event, task)"
<--!some html-->
Property.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
/**
* Service return Property value/values from the project property file
*
*/
#Injectable()
export class PropertyService {
//ReST Url for the PopertyService on the back end
private _url = '/grcworkflow/resources/grcWorkflow/environment/';
constructor(private _http: Http) {}
/**
* Method return an Observable<String -> Value> for any property
* Method make an http get call to the server to fetch the property
* #Param key for the property in the property file
*/
getValue(key: String): Observable<String> {
return this._http.get(this._url+key)
.map((response: Response) => <String> response.text())
.do(data => console.log('All: ' + data))
.catch(this.handleError);
}
private handleError(error: Response) {
return Observable.throw(error.json().error || 'Server error');
}
}
NOTE I have removed some function definitions and variable from the components which might be irrelevant.
I am trying to bind myEnviroment value of the app.component enviroment value. myEnviroment get set when proerty service returns a string. Although enviroment value still stays undefined .
I am looking for one way binding i.e when myEnvironment(parent) changes environment(child) should change too. But this doesn't seem to happen. Please help out here

Invalid argument for pipe 'AsyncPipe'

So i get this when i try to get my data from firebase
Invalid argument '{"-KCO4lKzEJPRq0QgkfHO":{"description":"teste","id":1457488598401,"resourceUrl":"tete","title":"test2"}}' for pipe 'AsyncPipe' in [listItems | async in ArcListComponent#2:10]
ArcListComponent
import { Component, OnInit } from "angular2/core";
import { FirebaseService } from "../shared/firebase.service";
import { ArcItem } from "./arc-item.model";
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async " class="arc-item">
<h3>{{ item.name}}</h3><a [href]="item.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.description }}</blockquote>
<hr>
</li>
</ul>
`
})
export class ArcListComponent implements OnInit {
listItems: string;
constructor(private _firebaseService: FirebaseService) {}
ngOnInit(): any {
this._firebaseService.getResources().subscribe(
resource => this.listItems = JSON.stringify(resource),
error => console.log(error)
);
}
}
firebase_service
import { Injectable } from "angular2/core";
import { Http } from "angular2/http";
import "rxjs/Rx";
#Injectable()
export class FirebaseService {
constructor(private _http: Http) {}
setResource(id: number, title: string, description: string, resourceUrl: string) {
const body = JSON.stringify({ id: id, title: title, description: description, resourceUrl: resourceUrl});
return this._http
.post("https://######.firebaseio.com/resource.json", body)
.map(response => response.json());
}
getResources() {
return this._http
.get("https://######.firebaseio.com/resource.json")
.map(response => response.json());
}
}
I know i am trying to show my data the wrong way but i do not know how to fix this. any help appreciated.
The async pipe expects an observable or a promise. http.get and map operator return observable, so you can set the returned object into the listItems property of your component. You don't need to subscribe in this case:
this.listItems = this._firebaseService.getResources();
Moreover the object, this element will "receive" must be an array to be able to use it within an ngFor. You service returns an object and not an array from Firebase. If you want to iterate over the keys of the object, you need to implement a custom pipe:
#Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push({key: key, value: value[key]);
}
return keys;
}
}
and use it like this:
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async | keys" class="arc-item">
<h3>{{ item.value.name}}</h3><a [href]="item.value.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.value.description }}</blockquote>
<hr>
</li>
</ul>
`,
pipes: [ KeysPipe ]
})
See this question for more details:
How to display json object using *ngFor
async pipe works with observables and/or promises. It does subscription for you, so you just have to pass an observable without subscribing to it in your code:
ngOnInit(): any {
this.listItems = this._firebaseService.getResources()
}

Categories