I'm using the MEAN Stack technology in a project, and I have the following schemas on frontend:
export interface ProjectSchema {
name: string;
acronym: string;
startDate: Date;
endDate?: Date;
tasks: string[];
}
and
export interface TaskSchema {
_id: string;
name: string;
}
On backend, tasks from projectSchema is of type object of tasks, who saves the string ids of tasks like:
tasks: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Task' } ]
So, what I want is to write on Frontend, the tasks names instead of the string ids which is what is saved on Backend.
I tried this, but can't understand what I'm doing wrong:
...
export class ProjectInfoComponent implements OnInit {
hasProject: boolean = false;
project!: ProjectSchema;
projectTasks: any;
taskNameList!: string[];
private _getProjectByAcronymRoute() {
this.projectService.getProjectByAcronym( this.route.snapshot.params['acronym'] )
.subscribe(
{
next: project => {
this.project = project;
this.hasProject = true;
this.projectTasks = this.project.tasks.map( t => t + "\n" );
this.taskNameList = [];
for (let i = 0; i < this.project.tasks.length; i++) {
this._getTaskByIdAux(this.project.tasks[i]);
}
this.taskNameList.map( t => t + "\n");
console.log(this.taskNameList);
console.log(this.project.tasks);
}
} )
}
private _getTaskByIdAux(myid: string) {
this.taskService.getTask(myid)
.subscribe(
{
next: taskaux1 => {
this.taskNameList.push(taskaux1.name);
}
} )
}
On HTML:
...
<div class="model-info__property">
<div class="model-info__property__key">
tasks:
</div>
<div class="model-info__property__value">
{{ projectTasks }}
</div>
</div>
<div class="model-info__property">
<div class="model-info__property__key">
tasksName:
</div>
<div class="model-info__property__value">
{{ taskNameList }}
</div>
</div>
Apparently, from tests that I've made, it is getting the name correctly, but it is not writing, and it seems array is somewhat strange when I log it.
Can you help me understand what I'm doing wrong and why it is not writing anything on taskNameList pls ?
Related
I am trying to display the data of an object on Angular like so
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}.
The object is issue from an API GET request but I'm not able to send it's value to my local variable myCharactere. Thank you for helping me out! Edit: Added code for clarification
Here is what I tried
TypeScript component
export class StatsComponent implements OnInit {
myCharactere: any;
statLookup = [
// Pourquoi est-ce un mauvais choix???
{ key: 'str', prefix: $localize`str`, suffix: $localize`enght`, couleur: 'bg-danger' },
{ key: 'dex', prefix: $localize`dex`, suffix: $localize`terity`, couleur: 'bg-primary' },
{ key: 'con', prefix: $localize`con`, suffix: $localize`stitution`, couleur: 'bg-warning' },
{ key: 'int', prefix: $localize`int`, suffix: $localize`elligence`, couleur: 'bg-success' },
{ key: 'sag', prefix: $localize`wis`, suffix: $localize`dom`, couleur: 'bg-info' },
{ key: 'cha', prefix: $localize`cha`, suffix: $localize`risma`, couleur: 'bg-dark' }
];
constructor(public myService: ServeurService) { }
ngOnInit(): void {
this.myService.getAllInfosById(this.myService.getPersonnageIdByName("Xefoul Snaromers")).subscribe(result => {
this.myCharactere = result
console.log(this.myCharactere);
});
getModifier(stat: number): string {
const mod = Math.floor((stat-10)/2)
return (mod<0)?'-':'+'+ mod.toString();
}
}
TypeScript Service
export class ServeurService {
personnages: any[] = [];
persoName = '';
constructor(private http_client: HttpClient) { }
getPersonnage(): Observable<ICharactere[]> {
return this.http_client.get<ICharactere[]>(this.serverCharacter).pipe(retry(4));
}
getAllInfosById(id: string) {
const myUrl = 'https://cegep.fdtt.space/v1/character/' + id;
return this.http_client.get<ICharactere>(myUrl).pipe();
}
setPersonnageName(name: string) {
this.persoName = name;
}
getPersonnageName():string {
return this.persoName;
}
getPersonnages() {
this.http_client.get<any>('https://cegep.fdtt.space/v1/characters').subscribe({
next: (val) => {
val.data.forEach((element: { data: any; }) => {
this.personnages.push(element);
});
}
});
return this.personnages;
}
getPersonnageById(id: string) {
const persoSelectionne = this.getPersonnages().find((x: { id: string; }) => x.id === id);
return persoSelectionne;
}
getPersonnageIdByName(name: string) {
const persoSelectionne = this.getPersonnages().find((n: {name: string; }) => n.name === name);
console.log("perso name service", persoSelectionne)
return persoSelectionne.id;
}
}
HTML to display
<div class="row text-center text-light bg-secondary mt-2 bg-transparent">
<div class="row mt-5">
<div class="col-2" *ngFor="let stat of statLookup">
<div class="{{stat.couleur}} mx-xxl-4 mx-2 mx-md-1 rounded-4">
<div class="fw-bold">{{stat.prefix}}<span class="d-none d-lg-inline">{{stat.suffix}}</span>
</div>
<div class="h2">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] ? getModifier(myCharactere.statistics[stat.key]) : ''}}
</div>
<div class="">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}
</div>
</div>
</div>
</div>
Model if it helps
export interface ICharactere {
error: string;
id: string;
name: string;
statistics: { [ key : string ]: number }
race: string;
player: string;
classe : string;
sousclasses: string;
level: number;
background: string;
synopsis: string;
image: string;
health: number;
currentHealth: number;
traits: {
trait: string;
description: string;
}[];
armorClass: number;
initiative: number;
speed: number;
}
Summarized from comments:
In your component you can use
serviceName.getAllInfosById("demochar").subscribe(console.log)
to manually make the request to the API and log the result. Please be aware that this.http_client.get<ICharactere>(myUrl) returns a cold observable. This means that nothing will be done until you actually call .subscribe to it.
Best practice:
Usually when you want to display data from an observable in your HTML template you define an observable and subscribe to it using async pipe.
The way to do this is to first define the observable in your component, like: info$ = this.serviceName.getAllInfosById("demochar").
Now in your HTML template you can use {{ (info$ | async).name }} to first subscribe to the observable (async pipe does this for you) and display the name property of the emitted value.
If you are actually using an observable like this.http_client.get<ICharactere>(myUrl), another way is to await the return value and store it in a this.myCharactere:
async getInfos(): void {
this.myCharactere = await firstValueFrom(this.myService.getAllInfosById(this.myService.getIdPersonnageByName("Xefoul Snaromers")));
}
how can I loop through the following object in Angular using *ngFor(suppousing there are many objects)? I can't find how to access "type" property. Also I wonder whether "Animals Catalog" property is correct? Thanks in advance.
{
"name":"Richard",
"lastname":"Garcia",
"age":32,
"pets":{
"Animals Catalog":{
"type":"cat",
"gender":"male"
}
},
}
that property is correct and you have to access it like this:
<h1 *ngFor="let item of items">{{item.pets['Animals Catalog'].type}}</h1>
you need to declare and access object.keys to use it in template
app.component.ts
objectKeys = Object.keys;
obj = {
name: 'Richard',
lastname: 'Garcia',
age: 32,
pets: {
'Animals Catalog': {
type: 'cat',
gender: 'male',
},
},
};
app.component.html
<div *ngFor="let key of objectKeys(obj)">{{ key + ' : ' + obj[key] }}</div>
reference: Object.keys()
Based on your code example:
interface
export interface User {
name: string;
lastname: string;
age: number;
pets: Pet;
}
export interface Pet {
[key: string]: PetDetail;
}
export interface PetDetail {
type: string;
gender: string;
}
component
<div *ngFor="let user of users">
<p>{{ getValue(user) }}</p>
</div>
#Component(...)
export class Component {
getValue(user: User): string {
const keys = Object.keys(user);
for (const key of keys) {
const isPetObject = this.validateType(user[key]);
if (!isPetObject) return user[key];
const pets = user[key];
const petKey = Object.keys(pets)[0]; // 'Animal Catalog'
return pets[petKey]?.type; //cat
}
}
private validateType(value: any): boolean {
return typeof value !== string && typeof value !== number;
}
}
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.
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.