Arrays in Angular 4 components. Unrecognized input parameters - html

I have a problem with the array declaration and the string interpolation in Angular 4 with TypeScript.
If I create this classes:
export class MyArrayProperty {
property1: string;
property2: string;
}
export class MyComponent {
#Input() object: ComplexObject;
myArray: MyArrayProperty[];
}
The ComplexObject is an Object with a lot of property:
ComplexObject {
myNumber: number;
myString: string;
// etc..
}
If I tried to create an array of instances of MyArrayProperty inside the component MyComponent, in this way:
export class MyComponent {
#Input() object: ComplexObject;
myArray: MyArrayProperty[] = [{
property1: 'hello',
property2: this.object.myString
}];
}
The field property1 is displayed correctly on the HTML page with the string interpolation: {{myArrayInstance.property1}} (myArrayInstance is obtained by ngFor).
But the property2 does not appear with string interpolation {{myArrayInstance.property2}}, even though this.object.myString is actually a string and I received an input object.
How does this happen and how can I solve this problem?

First of all why you're using a class for MyArrayProperty? You're just using it as a type, so you should go for an interface.
export interface MyArrayProperty {
property1: string;
property2: string;
}
Second, your code will fail because your're trying to access the property myString of the member object on component creation. But object is undefined before ngOnInit. Therefore you're accessing a property of undefined.
To solve your problem you could use a getter which transforms the object member to be displayed in your template.
export class MyComponent {
#Input()
public object: ComplexObject;
public get myArray() {
return Object.keys(this.object || {}).map(key => {
return {
property1: 'hello',
property2: this.object[key]
} as MyArrayProperty;
})
}
}

The reason for your error is that, the moment you are assigning the this.object.myString, the object is not available.
you can use ngOnChanges() which is executed everytime the #input property changes which in your case is object being inject in you component.
You can do something like this,
ngOnChanges (changes: SimpleChanges) {
if(this.object) {
myArray = [{
property1: 'hello',
property2: this.object.myString
}];
}}
To use ngOnChanges() you need to implement OnChanges interface to your component.
More on OnChanges() here
and
Angular LifeCycle Hooks

Related

Angular and JSON

I'm implementing a simple system to import JSON elements in Angular.
Everything works fine: I've used an interface, an observable and a directive. You can find the JSON here: http://jsonplaceholder.typicode.com/todos
Now, I want to use "completed", the boolean from JSON file, to display or not users when the page is loaded. There is a boolean "showUSer" and a method "displayUSer()" but I don't get it...
I cannot correctly retrieve this JSON data.
Any ideas ? :>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
interface JSP {
"userId": string;
"id": string;
"title": string;
"completed": boolean
}
#Component({
selector: 'app-product',
template: `<div class="display" *ngFor="let todo of todos">
<div>User Id: {{todo.userId}}</div>
<div >id: {{todo.id}}</div>
<div *ngIf="showUser">Title: {{todo.title}}</div>
</div>`,
styles: ['.display {margin-top: 20px; margin-bottom: 20px;}']
})
export class ProductComponent implements OnInit {
title: string = "Products List";
todos: JSP[];
showUSer: boolean;
constructor(private http: HttpClient){
}
ngOnInit(){
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => this.todos = result);
}
displayUSer(): void {
this.showUSer = this.todos.completed;
}
}
Nitpicks: Your question says to display or not users but your code seems to be display or not the title. Also why do you capitalize the 'S' in 'USers'?
The problem is this function which seems to ignore your actual data layout:
displayUSer(): void {
this.showUSer = this.todos.completed;
}
todos is a property of your controller. This is an array from the api call you make and it doesn't contain a completed property, so this.todos.completed will always be false. I'm a little surprised that you don't get an error compiling your typescript.
It looks like you want this flag to be on a 'todo item' basis and not page-wide, so this.showUSer doesn't make sense. Also you don't seem to be calling displayUSer to set the value in any case.
Since you are looking at an individual todo item and the query is simple, why don't you just look at the flag?
<div *ngIf="todo.completed">Title: {{todo.title}}</div>
If you are wanting to set a page-wide flag based on some critieria, you can do that when you subscribe to the results. Here I'm assuming that you will set the showUSer flag if any of the todo items is marked as completed:
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => {
this.todos = result;
this.showUSers = result.reduce((previous, current) => previous || current.completed, false);
});
Your JSON hasn't any json.completed value, but json[_].completed.

Reusable component with set Input() any type is a good practice?

I created a reusable component that takes a list of elements of the type any. He works on these elements and in the case of pressing the button returns the state of the list he got. I'm worried about losing typing in this case. Is this a safe solution? I would like the component to be reusable. Is it possible to dynamically cast from any type to a different type?
I tried to cast object in set Input() but it is not possible
export class ReusableTableComponent {
#Input() list: any[];
#Output() selectedItems: EventEmmiter = new EventEmmiter<any>;
selectedItemsList: any[] = [];
emitChanges() {
this.selectedItems.emit(this.selectedItemsList)}
}
The problem is, eventEmmiter is returning object type not specific type.
You can use a generic type:
export class ReusableTableComponent {
#Input() list: T[];
#Output() selectedItems: EventEmmiter = new EventEmmiter<T>;
selectedItemsList: T[] = [];
emitChanges() {
this.selectedItems.emit(this.selectedItemsList)}
}
Read more about generic types in TypeScript here:
https://www.typescriptlang.org/docs/handbook/generics.html
yes you can use dynamic types in Typescript
export class ReusableTableComponent<LIST, EVENTLIST> {
#Input() list: LIST[];
#Output() selectedItems: EventEmmiter = new EventEmmiter<EVENTLIST>;
selectedItemsList: EVENTLIST[] = [];
emitChanges() {
this.selectedItems.emit(this.selectedItemsList)}
}

Assign Correctly json Object to class

I have a class with an interface :
from another component in the code I read a json of my list of hero, and want to create a class for each hero (so it is easier to manipulate their data)
MY shop list CLASS =>
interface ShopItemInterface {
name: string;
image: string;
price: number;
description: string;
}
export class ShopItem implements ShopItemInterface {
public name: string;
public image: string;
public price: number;
public description: string;
constructor(obj: object) {
for (let key in obj) {
// this doesn't work and I don't know why, it is always false
if (this.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
}
}
LOADER COMPONENT CLASS =>
ngOnInit() {
this.http.get(this.jsonShopLocationFile).subscribe(res => {
for (let i = 0; i < res['items'].length; i++) {
this.shopItems.push(new ShopItem(res['items'][i]));
}
console.log(this.shopItems[0].name);
});
}
I can't find a way to correctly bind the json data to an object without listing all the parameters manually. ( which would be a mess and with 0 reusability)
How would you achieve that correctly ? Should I create a class and then directly call a function like hero.FromJSON(jsonObj) to manually set all the property? can I do this in some way in the constructor ?
thank you !
Because when you are constructing the object it does not have those properties, they are going to be undefined, just remove the test and it will work. Remember that interfaces are a TypeScript construct for the compiler and that you are running JavaScipt in your browser.
for (let key in obj) {
this[key] = obj[key];
}

How do I pass data from a json file into a html file in Angular 2?

I have this files.
wordCloud.ts
export class HomePageComponent {
wordcloudData : Array<string>;
private searchField : string;
private wordsApi : string;
wordClouds: any[] = [];
errorMessage: string;
listId:any = 1;
#Input() data : any;
#Input() testProperty : any;
#Input() dataField : string;
#Input() apiUrl : string;
constructor(public wordCloudListService: LexiconListService) {}
getWordCloudList() {
this.wordCloudListService.get('/assets/adhoc-search.json')
.subscribe(
wordClouds => {
EmitterService.get(this.listId).emit(wordClouds);
},
error => this.errorMessage = <any>error
);
}
ngOnInit() {
this.getWordCloudList();
EmitterService.get(this.listId).subscribe((wordClouds:any) => {this.wordClouds});
}
}
wordCloud.html
<div class="center" style="margin: 0 auto; width: 30%; padding-top: 100px;">
<cst-word-cloud [data]="{{wordClouds}}"></cst-word-cloud>
</div>
As you can see, I'm trying to load a json data and display the data into the wordCloud hmtl. I'm currently having difficulties doing this? Is there anything I'm doing wrong? How do I pass the value in the wordClouds array to display it?
In your ngOnInit() you are not getting the data of wordClouds in this.wordClouds.. just do this.
ngOnInit() {
this.getWordCloudList();
EmitterService.get(this.listId)
.subscribe((wordClouds:any) => {
this.wordClouds = wordClouds;
});
}
Do not emit the data. First of all is emitting a data is not the right approach. You should always emit the states like Boolean values or data which is used for a temporary basis. I would prefer not emitting the data, instead store first. Store it in some dataStore/ Class file. Make a class and store the data in it. After storing bind the template from that class getter method.

Typescript create a class from json

I have an interface IPost and a class Post, I want to create Post from json data, in Post constructor I get the json response where the interface IPost matches it.
I mean IPost interface is generated by this great tool json2ts
json2ts : generate TypeScript interfaces from JSON
import { IPost, Title, Guid, Content, Excerpt, Embedded, Links } from './Ipost.ts';
export class Post implements IPost {
Id: number;
Date: string;
DateGmt: string;
Guid: Guid;
Modified: string;
ModifiedGmt: string;
Slug: string;
Type: string;
Link: string;
Title: Title;
Content: Content;
Excerpt: Excerpt;
Author: number;
FeaturedImage: number;
CommentStatus: string;
PingStatus: string;
Sticky: boolean;
Format: string;
Links: Links;
Embedded: Embedded;
constructor(json: any) {
var self = this;
json.subscribe(res => {
var jsonRes: any = res.json();
self = jsonRes; //something like this
});
}
}
Can I assign the class Post to json directly since json is described the same as Post class!
Is there any other ways than assigning each property to its peer from json?
Just as you would JavaScript you'll have to iterate over each value, and assign that value to self, using standard loop. Assigning to self in your case, simply changes the value of self to the json value, it doesn't make any changes this itself.
json.subscribe(res => {
let json = res.json();
for (var prop in obj) {
if( obj.hasOwnProperty( prop ) ) {
this[prop] = obj[prop];
}
}
});
NOTE: => binds this to the outer context (for example the class you're working with) to this, so that you do not need to the intermediate self variable.