How do I display the id and title of the below Hero object?
The Hero interface below is mapped according to Firebase JSON response.
hero.component.ts
import {Component, Input} from 'angular2/core';
import {Hero} from '../model/hero';
#Component({
selector: 'hero-component',
template: `
{{hero | json }} this works. This display the Firebase JSON response as seen below
<br>
{{ Object.keys(hero)[0] }} this DOESN'T work
<br>
{{ hero[Object.keys(hero)[0]].title }}this also DOESN'T work
`
})
export class HeroComponent {
#Input()
hero:Hero;
}
hero.ts
export interface Hero {
[id: string]: {
createdAt: number;
isActive: boolean;
title: string;
updatedAt: number;
}
}
Firebase JSON response
{ "-KEMOfA5KFK98AXNhPY0": { "createdAt": 1459607745222, "isActive": true, "title": "Wind Hero", "updatedAt": 1459607745222 } }
update
instead of pipes: [KeysPipe]
use
#NgModule({
declarations: [KeysPipe],
exports: [KeysPipe],
}
export class SharedModule{}
#NgModule({
...
imports: [SharedModule],
})
original
You can't use Object.keys(hero)[0] in the template. No global references can be used, only members of the component.
Instead create a function on your component
getKeys(obj) {
return Object.keys(obj);
}
and then in the template
{{ getKeys(hero)[0] }}
Alternatively you can build a pipe that extracts the keys like for example https://stackoverflow.com/a/34074484/217408
#Pipe({ name: 'keys', pure: false })
export class KeysPipe implements PipeTransform {
transform(value: any, args: any[] = null): any {
return Object.keys(value);
}
}
then use it like
{{ (hero | keys)[0] }}
Don't forget to add the pipe to pipes: [KeysPipe] on the component where you want to use it or alternatively
bootstrap(App, [provide(PLATFORM_PIPES, {useValue: KeysPipe, multi:true})]);
Your code isn't working since Object isn't available in your component.
You could try something like this:
#Component({
selector: 'hero-component',
template: `
{{hero | json }} this works. This display the Firebase JSON response as seen below
<br>
{{ obj.keys(hero)[0] }} this DOESN'T work
<br>
{{ hero[obj.keys(hero)[0]].title }}this also DOESN'T work
`
})
export class HeroComponent {
#Input()
hero:Hero;
obj = Object;
}
or using a method like Günter suggested in his answer.
Related
This is my component's typescript code:
import { Component, Input, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { Observable } from 'rxjs';
#Component({
selector: 'app-counter-output',
templateUrl: './counter-output.component.html',
styleUrls: ['./counter-output.component.css']
})
export class CounterOutputComponent implements OnInit {
counter!: number;
counter$!: any;
constructor(private store: Store<{ counter: { counter: number } }>) {
}
ngOnInit(): void {
this.store.select('counter').subscribe((data) => {
this.counter = data.counter;
console.log(data);
});
this.counter$ = this.store.select('counter');
console.log(this.counter$);
}
}
This is its HTML template:
<div>This is the counter:{{(counter$|async).counter}}</div>
<div>This is the counter:{{counter}}</div>
There is an error in line no 1 in the HTML file.
When I am subscribing in the typescript file I am able to get the value, but when I am using it as an observable it is showing an error.
Set a type to counter$.
export interface Counter {
counter: number;
}
....
counter$: Observable<Counter>;
Of course, you have to be sure that the selector returns Counter as data.
EDIT: If you insist to use any (which beats the purpose of using Typescript), you can use the $any() type cast function to suppress this error.
<div>This is the counter:{{ ($any(counter$|async)).counter }}</div>
Have you tried to actually define your counter$ property as an Observable?
Why declare it as any if you already know what type of data is?
export interface myInterfaceData {
counter: number;
}
counter$!: Observable<myInterfaceData>;
<ng-container *ngIf="{ myCounter: counter$ | async } as observable">
<div>
This is the counter: {{ observable.myCounter?.counter }}
</div>
</ng-container>
I'm having real trouble converting a JSON array to collection of objects in Angular. I've not used Angular for some time, please forgive me if any of my terminology is incorrect.
I have the following products.json file in my project:
[
{
"id": 1,
"name": "Pen",
"description": "The finest blue pen.",
"relatedProducts": [2]
},
{
"id": 2,
"name": "A4 Paper",
"description": "A4 sized printer paper.",
"relatedProducts": [1]
}
]
This product.ts file:
export class Product {
Id: number;
Name: string;
Description: string;
RelatedProducts: Array<number>;
}
My app.component.ts file:
import * as ProductsJson from '../../json-data/products.json';
import { Component, OnInit } from '#angular/core';
import { Product } from 'src/app/models/product';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
productsJson: any = ProductsJson;
products: Array<Product>;
ngOnInit() {
this.products = <Product[]>this.productsJson;
console.log(this.products);
}
}
And finally my app.component.html:
<ul>
<li *ngFor="let p of products">
{{p.Id}} {{p.Name}}
</li>
</ul>
My console log shows the JSON data but it seems as though I'm making some error trying to convert it to a list of Product objects. This is also preventing me from successfully using ngFor in my view as the console error shows, so nothing shows on the page. How can I perform this conversion correctly so the loop works and this data is shown on the view?
you can use class-transformer which helps you to convert plain javascript objects to real es6 classes. With this real instances you can also use class methods.
import { Component, OnInit } from "#angular/core";
import ProductsJson from "./products.json";
import {plainToClass} from "class-transformer";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
productsJson: any = ProductsJson;
products: Array<Product>;
constructor() {}
ngOnInit() {
//this.products = <Product[]>this.productsJson;
this.products = plainToClass(Product, this.productsJson as []);
console.log(this.products);
}
}
export class Product {
id: number;
name: string;
description: string;
relatedProducts: Array<number>;
getId(){
return this.id + "_ID";
}
}
template:
<ul>
<li *ngFor="let p of products">
{{p.getId()}} {{p.id}} {{p.name}}
</li>
</ul>
**- Use interface instead of class for Product.**
export interface Product {
id: number;
name: string;
description: string;
relatedProducts: number[];
}
**- If you need a class, then specify the parameterised constructor to assign variable values.**
export class Product {
id: number;
name: string;
description: string;
relatedProducts: number[];
constructor(product) {
this.id = product.id;
this.name: product.name;
this.description: product.description;
this.relatedProducts: product.relatedProducts;
}
}
**and then inside the component you can use**
this.products = new Product(this.productsJson); // if Product is a class
OR
this.products = this.productsJson; // if Product is an interface
look at stackblitz
Let me know still you have an issue..
thanks
Your model should look like below,
export interface Product {
id: number;
name: string;
description: string;
relatedProducts: number[];
}
Change the products as
products: Product[];
and then,
this.products = this.productsJson;
Adding to Kushal Shah answer
this also works.
import { Component, OnInit } from "#angular/core";
import ProductsJson from "./products.json";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
products = <Product[]>ProductsJson;;
constructor() {}
ngOnInit() {
console.log(this.products);
}
}
export class Product {
id: number;
name: string;
description: string;
relatedProducts: Array<number>;
}
step by step:
Component.ts
The first, you have unuseless variables you have reduce keeping just one 'products' and initializing just like that the following code.
In ngOnInit() you must to load the array initialized before. The way to do that is casting with type any cause importing from local json generates another data model, you have to access to the property 'default' and that's all.
import * as ProductsJson from '../../json-data/products.json';
import { Component, OnInit } from '#angular/core';
import { Product } from 'src/app/models/product';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
products: Array<Product> = new Array<Product>();
ngOnInit() {
this.products = (ProductsJson as any).default;
console.log(this.products);
}
}
Component.html
Another point to change is properties naming you have 'Id' & 'Name' instead of 'id' and 'name'.
<ul>
<li *ngFor="let p of products">
{{p.id}} {{p.name}}
</li>
</ul>
Entities
Product Class have to change the properties naming without Capital letters to have a correspondence with the Json Data or change Json data model to the Class anyway.
If I were you I will change also Product, using interface instead Class
export interface Product {
id: number;
name: string;
description: string;
relatedProducts: Array<number>;
}
UPDATE 1 [ You can go declarative way of rxjs too ]..
Use of operator of rxjs to create new stream of observable from your json file.
import {of} from 'rxjs'
productsJson = of <Product []>(ProductsJson);
export interface Product{
id: number;
name: string;
description: string;
relatedProducts: Array<number>;
}
and then in your template you can simply use async pipe ...
<ul>
<li *ngFor="let p of productsJson | async">
{{p.id}} {{p.name}}
</li>
</ul>
Update 2 [ You can simply use JSON.parse() and JSON.stringify() ]..
productsJson: any = JSON.parse(JSON.stringify(ProductsJson));
and then use this in template like this...
<div *ngFor = "let item of productsJson">
<span> {{item.id }} </span>
</div>
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.css'],
})
export class HelloWorldComponent implements OnInit {
jsonString: string = '[{"title":"test"},{"title":"test2"}]';
jsonObj: Array<object>;
jsonObj2: Array<object> = [{ title: 'test' }, { title: 'test2' }];
constructor() {
this.jsonObj = JSON.parse(this.jsonString);
this.jsonString = JSON.stringify(this.jsonObj2);
}
ngOnInit() {}
}
I am trying to display some information which is fetched from an external API on a web page by using Angular's string interpolation.
When no information is fetched or hasn't "arrived" yet, I want to display 'N/A'.
I have tried the following approach, however I get an error saying:
'Can't read property 'name' of undefined' on line 2 of the following html code.
How can I show N/A while waiting for the response to be defined?
app.component.html:
<div id="api-info">
{{ !!this.apiInfo.name ? this.apiInfo.name : 'N/A' }}
</div>
app.component.ts:
import { ApiInfo } from 'src/app/apiInfo.model'
import { ApiService } from 'src/app/api.service'
(...)
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
apiInfo: ApiInfo;
apiInfoSub: Subscription;
constructor(apiService: ApiService) {}
ngOnInit() {
// apiService.apiInfo is a Subject<ApiInfo> where the API response is stored
this.apiInfoSub = this.apiService.apiInfo.subscribe(
(response) => {
this.apiInfo = response;
}
);
}
ngOnDestroy() {
this.apiInfoSub.unsubscribe();
}
}
apiInfo.model.ts:
export class ApiInfo {
public name: string,
public id: number,
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
I suggest to not subscribe within the component. Better use the async pipe instead and check as follows...
<div id="api-info" *ngIf="(apiInfo$ | async as apiInfo); else pending">
{{ apiInfo.name }}
</div>
<ng-template #pending>
n/a
</ng-template>
Which also allows you to style the n/a differently quite easy
It is better to use async pipe, instead of subscribing and unsubscribing to stream manually. That makes the code cleaner and in the html use expression:
{{(stream$|async)?.name || 'N/A'}}
Here is code sample: https://stackblitz.com/edit/angular-nknwuq
please change your app.component.html to below:
<div id="api-info">
{{ apiInfo?.name ? apiInfo.name : 'N/A' }}
</div>
this should resolve the issue.
apiInfo is undefined at start. The subscribe does not resolve immediately, so the apiInfo = response is set after some time. Maybe use <div id="api-info" *ngIf="apiInfo">
Or initialize on declaration: apiInfo: ApiInfo = <ApiInfo>{};
my boss decided to implement Angular2 into our project. I'm preety new to this technology. I'm trying to display JSON from url, but the result is not as I expected. *ngFor doesn't display any data.
This is the code:
vehicle.service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class VehicleService {
constructor(private http: Http){ }
getVehicle() {
return this.http.get('https://jsonplaceholder.typicode.com/posts')
.map(res => res.json());
}}
vehicle.component.ts
import { Component } from '#angular/core';
import { VehicleService } from './vehicle.service';
#Component({
moduleId: module.id,
selector: 'vehicle-json',
templateUrl: 'vehicle.html',
providers: [ VehicleService ]
})
export class VehicleComponent {
vehicle: Vehicle[];
constructor(private vehicleService: VehicleService){
this.vehicleService.getVehicle().subscribe(vehicle => {
/*console.log(vehicle);*/this.vehicle = vehicle;
});
}
}
interface Vehicle {
id: number;
title: string;
body: string;
}
vehicle.html
<div *ngFor="let vehicle of vehicles">
<h3>{{vehicle.title}}</h3>
<p>{{vehicle.body}}</p>
</div>
test 1-2-3
The output is: "test 1-2-3". I checked if the jsons will be displayed inside the console using: console.log(vehicle); - it worked, it returns Array of Objects.
What am I doing wrong? Any ideas how to fix my code?
You have an error:
this.vehicleService.getVehicle().subscribe(vehicle => {
this.vehicle = vehicle;
})
but in your html you refer to let vechicle of vehicles
You should add an "s" to your this.vehicle to your subscription like so:
this.vehicleService.getVehicle().subscribe(vehicle => {
this.vehicles = vehicle;
})
Edit: Just forgot to meantion to change the local variable vehicle: Vehicle[] to vechicles: Vehicle, but that you figured out that yourself! ;)
How do I display this specific JSON using *ngFor?
{
"status": 0,
"dallases": [{
"vehicle_id": 17954,
"dallassettings": "3",
"dallasupdated": "False",
"dallas_list": [{
"number": 666111222,
"auth": 3
}, {
"number": 666777888,
"auth": 4
}, {
"number": 123454321,
"auth": 4
}]
}
}
vehicle.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Jsonp, Response } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class VehicleService {
constructor(private http: Http) { }
getVehicle() {
return this.http.get('myURL')
.map(res => res.json());
}
}
vehicle.component.ts
import { Component, enableProdMode } from '#angular/core';
import { VehicleService } from './vehicle.service';
enableProdMode();
#Component({
//moduleId: module.id,
selector: 'vehicle-json',
templateUrl: './vehicle.html',
providers: [VehicleService]
})
export class VehicleComponent {
//vehicles: Vehicle[];
vehicles: GeneralVehicle[];
constructor(private vehicleService: VehicleService) {
this.vehicleService.getVehicle().subscribe(vehicle => {
this.vehicles = vehicle;
});
}
}
/*interface Vehicle {
id: number;
title: string;
body: string;
}*/
interface GeneralVehicle {
status: number;
dallases: Vehicle[];
}
interface Vehicle {
vehicle_id: number;
dallassettings: number;
dallasupdated: string;
dallas_list: DallasList[];
}
interface DallasList {
number: number;
auth: number;
}
When I worked on dummy data it was simply, but this JSON structure is more complex. I tried using Pipe, but I'm not sure if I'm using it correctly.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'keys' })
export class VehiclePipe implements PipeTransform {
transform(value, args: string[]): any {
let keys = [];
for (let key in value) {
keys.push(key);
}
return keys;
}
}
That's the *ngFor
*ngFor="let vehicle of vehicles | keys"
I'd like to dislay status once, and then repeat all the dallases (Vehicle[]).
At least you have one error here:
dallassettings: '3' // string
but in your interface you have:
dallassettings: number;
Then if you implement Matthiases implementation, put a *ngIf-statement, since I suspect you get your data async, so view is rendered before data retrieved, which would cause a undefined error.
<div *ngIf="vehicles"> // here!
<h1>{{ vehicles.status }}</h1>
<div *ngFor="let vehicle of vehicles.dallases">
<div>ID: {{vehicle.vehicle_id}}</div>
<div>Settings: {{vehicle.dallassettings}}</div>
<div>Updated: {{vehicle.dallasupdated}}</div>
<div *ngFor="let d of vehicle.dallas_list">
<div>number: {{d.number}}</div>
<div>auth: {{d.auth}}</div>
</div>
</div>
</div>
EDIT your status is undefined, since you you are using the wrong attribute it should be vehicles.status with "s". When I edited this post, I also added the "s" to the answer, so that it would be correct :)
You have to loop through 2 ngFor's:
<div *ngFor="let vehicle of vehicles | keys">
<h1>{{ vehicle.status }}</h1>
<ul *ngFor="let dallas of vehicle.dallases">
<li>{{ dallas | json }}</li> //Show the data you want
</ul>
</div>
vehicles is an object. I see no need to iterate over the keys with the pipe you proposed. You can access the members you need directly. You should iterate over the dalasses property of vehicles - which is an array. Then display the array member as you require. E.g. with a json pipe to get in text format, you you might also implement custom formatting inside the template through properties.
Example:
<div>
<h1>{{ vehicle.status }}</h1>
<div *ngFor="let vehicle of vehicles.dallases">
<div>ID: {{vehicle.vehicle_id}}</div>
<div>Settings: {{vehicle.dallassettings}}</div>
<div>Updated: {{vehicle.dallasupdated}}</div>
<div *ngFor="let d of vehicle.dallas_list">
<div>number: {{d.number}}</div>
<div>auth: {{d.auth}}</div>
</div>
</div>
</div>