How can I pull an object from component to html angular?
Here is my component file code:
export class DetailsComponent implements OnInit {
authorID: string;
authorData: any = {};
constructor(
private router: Router,
private route: ActivatedRoute,
private httpService: HttpService
) {}
ngOnInit() {
this.authorID = this.route.snapshot.paramMap.get("id");
console.log(this.authorID);
this.getSingleAuthor();
}
getSingleAuthor() {
let observable = this.httpService.getOneTask(this.authorID);
observable.subscribe((data) => {
this.authorData = data;
console.log(this.authorData);
});
}
}
and here is my html code:
<button class="btn btn-info btn-sm" [routerLink]="['/write', 1]">Add Quote</button>
<div class="info" *ngFor="let info of authorData">
<p>Quote by {{info.name}}: </p>
<table class="table border">
<thead class="thead-light">
<tr>
<th>Quote</th>
<th>Votes</th>
<th>Action available</th>
</tr>
</thead>
</div>
I tried to pull out author name from an object in component but it didnt work out.
I tried to for loop and it worked but in my console it showed this error:
This is my author model:
const AuthorSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Name is Required"],
minlength: [3, "Must be longer than 3 characters"]
},
author_quote: [{
quote: {
type: String,
minlength: [3, "Must be longer than 3 characters"]
},
vote: Number
}]
})
Cannot find a differ supporting object '[object Object]' of type 'object'
Ehy !
i assume that authorData is just an object of the kind:
{
name
age
...
}
So it's quite difficult that you need to iterate it with an *ngFor that is instead used for collections.
You should just ensure to render the portion of html that uses those data when they are ready:
Leave the authorData to undefined in your component: authorData: any;
Use an *ngIf in your template, so you will render that html when the author's data are available:
<div *ngIf="authorData" class="info">
<p>Quote by {{ authorData.name }}: </p>
...
</div>
As alternative you can avoid to subscribe to the observable in the component and use an async pipe approach
Related
I have a form with some input fields.
When I submit the form I want to add a row to my table dataSource with the new data.
I have a component for the form that looks like that:
FORM HTML
<form (submit)="submitForm($event)">
<app-form-element align="center" *ngFor="let el of fields| keyobject" [value]="el.value.value" [type]="el.value.type">
</app-form-element>
<button>Save User</button>
</form>
FORM TS
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
fields!: object;
constructor() { }
ngOnInit(): void {
this.newForm();
}
newForm() {
this.fields = [{ value: "Name", type: "text" },
{ value: "Surname", type: "text" },
{ value: "Email", type: "email" }];
}
tbc = new TableComponent;
submitForm(event: any) {
let newUser = new User();
newUser.name = event.target.Name.value;
newUser.surname = event.target.Surname.value;
newUser.email = event.target.Email.value;
this.tbc.addValue(newUser);
event.preventDefault();
}
}
export class User {
name!: string;
surname!: string;
email!: string;
}
TABLE HTML
<table *ngIf="show">
<tr>
<th *ngFor="let column of headers">
{{column}}
</th>
<th>Commands</th>
</tr>
<tr *ngFor="let row of dataSource | keyobject; let i = index">
<td *ngFor="let col of headers">
{{row.value[col]}}
</td>
<td>
<button class="btn btn-default" type="button" (click)="deleteValue(i)">Delete</button>
</tr>
</table>
TABLE TS
export class TableComponent implements OnInit {
headers = ['name', 'surname', 'email'];
dataSource: any = [
{ id: 1, name: "test", surname: 'test', email: "test#gmail.com"},
];
ngOnInit(): void {
}
addValue(user: User) {
let id = this.dataSource.length + 1;
this.dataSource = [...this.dataSource, { id: id, name: user.name, surname: user.surname, email: user.email, save: false }];
this.reload();
}
deleteValue(id: any) {
this.dataSource.splice(id, 1);
this.reload();
}
public show = true;
reload() {
this.show = false;
setTimeout(() => this.show = true);
}
}
When I call the addValue function in the Form.ts it works but the dataSource doesn't get updated.
Debugging the code everything works and it looks like the record is being added to the dataSource but the table dataSource doesn't actually have the new record so it doesn't get displayed.
Notice that my deleteValue is working fine and is deleting the row from the dable and from the dataSource
I'm new to angular so any help is appreciated
I think the problem is that the changes on dataSource array from your child component are not automatically detected on push. You can force the change detection using detectChanges from ChangeDetectorRef :
#Component({
...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TableComponent implements OnInit {
contructor(
private cdr: ChangeDetectorRef
) {}
dataSource: any = [
{ id: 1, name: "test", surname: 'test', email: "test#gmail.com"},
];
addValue(user: User) {
let id = this.dataSource.length + 1;
this.dataSource.push({ id: id, name: user.name, surname: user.surname, email: user.email});
this.reload();
}
public show = true;
reload() {
// here you can force the change detection
this.cdr.detectChanges();
...
}
}
Even though, the other solution of the changeDetector might work, it's not the best approach to tell angular to refresh. It's better to instead, just put your code in a way that angular will notice that needs to change.
I believe that in angular, a push into the array, it's not detected as a new change.
I think instead of doing the push, you could do the following:
this.dataSource = [...this.dataSource, { id: id, name: user.name, surname: user.surname, email: user.email}]
Basically, you would create a new array that contains the old array + the new data.
Anyways, to be sure this is a correct answer, could you provide the actual code you have, not a small cut of it, with both TS and HTML Templates ?
I cant figure it out where the problem is. This is my template:
<h1>Details for {{ project.projectName }} </h1>
<h6>Project Description: {{ project.description }}</h6>
<h6>Project Stage: {{ project.stage }}</h6>
<hr />
<app-employee-list></app-employee-list>
<app-tickets-list></app-tickets-list>
This is the service where i get a single project:
interface GetResponse {
_embedded: {
projects: Project[];
};
}
#Injectable({
providedIn: "root",
})
export class ProjectService {
private baseUrl = "http://localhost:8080/api/projects";
renderRoleAssignment$: Subject<boolean>;
constructor(private httpClient: HttpClient) {
this.renderRoleAssignment$ = new Subject<boolean>();
}
getProjectList(): Observable<Project[]> {
return this.httpClient
.get<GetResponse>(this.baseUrl)
.pipe(map((response) => response._embedded.projects));
}
getProject(id: number): Observable<Project> {
return this.httpClient.get<Project>(this.baseUrl + "/" + id);
}
}
This is the project class:
export class Project {
id: number
_links?: Links;
projectName: string;
description: string;
stage: string;
}
export class Links {
self: { href: string };
}
And this is my component:
export class ProjectDetailsComponent implements OnInit, OnDestroy {
project: Project;
getProjectSub: Subscription;
private baseUrl = "http://localhost:8080/api/projects/";
constructor(
private route: ActivatedRoute,
private projectService: ProjectService
) {
this.getProjectSub = new Subscription();
}
ngOnInit() {
this.projectService.renderRoleAssignment$.next(false);
this.listProject();
}
listProject() {
const projectId = this.route.snapshot.params["id"];
this.getProjectSub = this.projectService
.getProject(projectId)
.subscribe((data) => {
this.project = data;
console.log(this.project)
this.addIdToProject(projectId);
console.log(this.project.id)
});
}
addIdToProject(id: number) {
this.project.id = id;
console.log(this.project.id)
this.route.params.subscribe((params: Params) => {
this.project.id = id;
});
}
ngOnDestroy() {
this.getProjectSub.unsubscribe();
}
}
The stack trace of the error:
core.js:4196 ERROR TypeError: Cannot read property 'projectName' of undefined
at ProjectDetailsComponent_Template (template.html:1)
at executeTemplate (core.js:7446)
at refreshView (core.js:7315)
at refreshComponent (core.js:8453)
at refreshChildComponents (core.js:7108)
at refreshView (core.js:7365)
at refreshEmbeddedViews (core.js:8407)
at refreshView (core.js:7339)
at refreshComponent (core.js:8453)
at refreshChildComponents (core.js:7108)
{projectName: "Game App", description: "A simple game", stage: "Completed", _links: {…}}
1
1
So as you can see from the logs the project is there, the id that i add are defined.
But for some reason it cant find the project properties and display them.
Here is whats going on: You are declaring project: Project at the top. Your html is trying to read a property off of project it is not defined yet so thats why you get Cannot read property 'projectName' of undefined as that call is made in the NgOnInit and is async.
Here is how to fix it as long as your service call is returning the correct data: Short way: Initialize project at the top. Long way: It would be a good idea to make Project an interface and call it IProject then create a class called Product that implements IProject.
export interface IProject {
id?: number
_links?: Links;
projectName?: string;
description?: string;
stage?: string;
}
export class Project implements IProject {
constructor(
public id: number,
public _links?: Links,
public projectName?: string,
public description?: string,
public stage?: string,
){}
}
Then in your component html:
project: IProject = {};
and of course in your service:
getProject(id: number): Observable<IProject> {
return this.httpClient.get<IProject>(`${this.baseUrl}/${id}`);
}
finally in your html using the optional chaining operator:
<h1>Details for {{ project?.projectName }} </h1>
Generally speaking, we should be using optional chaining wherever possible. In this case, this would save you some trouble:
<h1>Details for {{ project?.projectName }} </h1>
or if it makes more sense
<h1 *ngIf="project?.projectName">Details for {{ project.projectName }} </h1>
This error is thrown because you have declared project variable, but initialised it asynchronously some time after angular tried to render the view.
The same of course is valid for the rest of your html, wherever you are trying to access an object property that might be null or undefined.
This question already has answers here:
How do I return the response from an Observable/http/async call in angular?
(10 answers)
Closed 5 years ago.
I want to assign a json response which contains an array with the following:
0
:
{ID: 2, NAME: "asd", PWD_EXPIRY_IN_DAYS: 30}
1
:
{ID: 1, NAME: "Admin", PWD_EXPIRY_IN_DAYS: 30}
I have a local variable of type groups, which is like so
export class Group {
id: string;
name: string;
pwd_expiry_in_days: string;
}
Now I created an object of type Group in my component which I want to assign the json reply into, the following is not working and is showing undefined. Here is my code:
import { Injectable, Provider, ModuleWithProviders,Component, OnInit } from '#angular/core';
import { Http, Headers, Response, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import {Group} from '../../_models/group'
import 'rxjs/add/operator/map';
interface Validator<T extends FormControl> {
(c: T): { [error: string]: any };
}
#Component({
selector: 'form1',
templateUrl: './form1.html',
moduleId: module.id,
})
export class Form1Component {
public roles: Group; // <--- variable to feed response into
private getGroups() : Observable<any> {
console.log("In Groups");
var responseAsObject : any;
let _url = groupsURL";
let headers = new Headers();
headers.append('X-User', sessionStorage.getItem('username'));
headers.append('X-Token', sessionStorage.getItem('token'));
headers.append('X-AccessTime', sessionStorage.getItem('AccessTime'));
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers });
return this.http.get(_url, options)
.map(response => {
var responseAsObject = response.json();
console.log(responseAsObject); //<--- proper response
return responseAsObject;
})
}
constructor(private http: Http) {
this.getGroups()
.subscribe(data => {
this.roles = data;
console.log(this.roles); //<--- proper response
});
console.log(this.roles); //<----- undefined, I need the roles variable so I can use it on the front end
}
How can I fix this? I've been told its an Async issue, simply assigning this.roles = data (from the json response) is not working and shows up as undefined in my component (anywhere outside the scope of the subscription).
What is the proper method of assigning a response into my local variable in this case?
UPDATED with template to view the object, also being viewed as undefined:
<div class="form-group" [ngClass]="{'has-error':!complexForm.controls['group_id'].valid}">
<label>Group ID</label>
<div class="row">
<div class="col-md-4">
<select name="group_id" id="group_id" class="form-control" [formControl]="complexForm.controls['group_id']" data-width='200px'>
<option *ngFor="let role of roles" [value]="role.id">
{{role.name}}
</option>
</select>
</div>
</div>
</div>
thank you!
What is the proper method of assigning a response into my local variable in this case?
You're not doing it wrong. You just need to be better prepared for it for be undefined and/or empty at the initial stages of the component construction.
The easiest thing to do is simply do an *ngIf="someArray.length" on an html node before iteration. something like:
// html
...
<div class="row" *ngIf="roles.length"><!-- Prevents un-populated array iteration -->
<div class="col-md-4">
<select class="form-control">
<option *ngFor="let role of roles" [value]="role.id">
{{role.name}}
</option>
</select>
</div>
</div>
There are some improvements you can make to your typescript as well, such as not changing the pointer of the array- this may save you some trouble later on -so instead of this.roles = data, use this.roles.length = 0; this.roles.push(...data);. For more info read up on Angular Change Detection.
// ts
export class Form1Component implements OnInit{
public roles: Array<Group> = []; // <--- isn't this an Array?
...
private getGroups() : Observable<any> {
var responseAsObject : any;
...
return this.http.get(_url, options)
.map(response => {
var responseAsObject = response.json();
return responseAsObject;
});
}
constructor(private http: Http) {}
ngOnInit(){
this.getGroups()
.subscribe(data => {
let groups = data.map(item=>{
return new Group(item)
});//<--- If you want it to be of type `Group`
this.roles.length = 0;
this.roles.push(...groups);
});
}
...
}
You second console log will run before your api call because api calls are asynchronous. Please try to make the type of role any like publice role: any if its works then you have to modify your Group model.
i have an Object type of json that I cant read...
this is my json:
body: {
"111": {
"name": "name1",
"status": 10000
},
"222": {
"name": "name2",
"status": 20000
},
"333": {
"name": "name3",
"status": 30000
}
}
and I want to know how to present it in my html?
this is my attempt:
<md-content>
<h1 align="center">{{title}}</h1>
<h2>list of items:</h2>
<div class="list-bg" *ngFor="#item of items | async">
ID: {{item.name}} <p></p> Number of Items: {{item.status}}
</div>
</md-content>
not only that it dosent work, im trying to figure out how to read each line the object id's(those: 111, 222, 333)
this is my model:
export interface MyModel {
name: string;
status: number;
}
this is my component:
export class MyCmp implements OnInit {
retJson: Observable<MyModel[]>
constructor(private _myService: MyService) {};
public showData(): void {
this.retJson = this._myService.getData();
}
}
thanks!
You haven't included how you load your json, so I'll write a more general example:
interface MyModel {
name: string;
status: number;
}
interface MyResponse {
body: { [id: string]: MyModel }
}
let response: MyResponse = JSON.parse(RESPONSE_STRING);
Object.keys(response.body).forEach(id => {
let model = response.body[id];
console.log(`id: ${ id }, name: ${ model.name }, status: ${ model.status }`);
});
What's missing here is the RESPONSE_STRING, which I'm not sure how you get.
As for the template, I'm not an angular developer but as far as I understand ngFor is used for arrays, and you don't have an array in your json structure.
ngFor loop only will work for array. Your body json is key value object, so it wouldn't work.
If you want to make it work, it's either:
you use an additional pipe to convert key value object to array using pipe, solution discussed here: Iterate over TypeScript Dictionary in Angular 2, then you can use
*ngFor="let item of items | async | mapToIterable"
Or process it in your ts file, instead of
this.retJson = this._myService.getData();, use this._myService.getData().subscribe(x => this.retJson = processToArray(x))
I'm trying to write a pipe that filters an array of JSON objects. Every object has 3 keys that are booleans - demo, github, finished and I want to be able to input these into my filter, and present only the objects where the key is true. I don't need to input multiple values, a single string (key) is enough.
So far, no matter what I input into the filter, the page shows no data. If I remove the filter completely I get everything defined in the service. There are also no error messages logged.
So I have a service that provides the pages:
import { Injectable } from 'angular2/core';
export class Page {
constructor(public img: string, public name: string, public repo: string, public description: string, public demo: boolean, public github: boolean, public finished: boolean) { }
}
#Injectable()
export class PagesService {
getPages() {
return [
new Page('./app/images/placeholder.png', 'veryNiceWords', 'https://github.com/Shooshte/veryNiceWords', 'A hobby app, made to enable posting, rating and sharing quotes over social networks. Work in progress.', false, true, false),
new Page('./app/images/placeholder.png', 'ZIC IJS', 'https://github.com/Shooshte/ZIC', 'Refurbishing of on old library webpage with AngularJS.', false, true, false),
new Page('./app/images/weather.png', 'Show the Local weather', 'http://codepen.io/shooshte/pen/NxOwOX', 'A freeCodeCamp exercise, designed to show the local weather.', true, false, true),
new Page('./app/images/calculator.png', 'Calculator', 'http://codepen.io/shooshte/pen/qbjJdy', 'A freeCodeCamp exercise, which requires you to build a javascript calculator.', true, false, true),
new Page('./app/images/github.png', 'MTGO Draft Replayer', 'https://github.com/Shooshte/MTGO-Draft-Replayer', 'A simple web app that opens a MTGO draft log file, and re-creates the draft from it.', false, true, false),
new Page('./app/images/codeeval.png', 'codeEval', 'https://github.com/Shooshte/CodeEval', 'CodeEval challenges solutions written in javascript and posted to gitHub.', false, true, true)
];
}
}
Here is where I call the service OnInit and define the pipe:
import { Component } from 'angular2/core';
import { ViewEncapsulation } from 'angular2/core';
import { Page, PagesService } from './pages.service';
import { Pipe, PipeTransform } from 'angular2/core';
#Pipe({ name: 'pagesFilter' })
export class pagesFilter {
transform(pages, [key]) {
return pages.filter(page => {
return page.key === true;
});
}
}
#Component({
selector: 'portfolio',
templateUrl: '/app/views/portfolio.html',
styleUrls: ['../app/styles/PortfolioMobile.css', '../app/styles/PortfolioOther.css'],
pipes: [pagesFilter],
encapsulation: ViewEncapsulation.None
})
export class PortfolioComponent {
filter = 'everything';
pages: Page[];
constructor(private _pagesService: PagesService) { }
ngOnInit() {
this.pages = this._pagesService.getPages();
}
}
This is how I use the pipe in my html:
<div class="portfolioContainer">
<div class="displayHack"></div>
<div *ngFor="#p of pages | pagesFilter:demo" class="portfolioPageContainer">
<img [attr.src]="p.img" class="portfolioThumbnail">
<h2>{{ p.name }}</h2>
<a [attr.href]="p.repo">
<div>
<p>{{ p.description }}</p>
</div>
<p class="portfolioRepoLink">See the Code!</p>
</a>
</div>
<div class="displayHack"></div>
</div>
You could try this instead:
#Pipe({ name: 'pagesFilter' })
export class pagesFilter {
transform(pages, [key]) {
return pages.filter(page => {
return page[key] === true; // <------
});
}
}
In your case you try to access the property with name "key" but not with the name corresponding to the content of the key parameter.
Moreover if you want to use the value "demo" (not to evaluate the expression "demo"), you need to use the following:
<div *ngFor="#p of pages | pagesFilter:'demo'"
class="portfolioPageContainer">