Angular ngFor sees string array as one string - arrays

I have a popup dialog, which recieves a list of strings from the backend. I want to print every string as a list item, using ngFor. But when the dialog pops up, the whole array is shown as one concatenated string.
needs-dialog.component.ts
import { Component, Inject, OnInit } from '#angular/core';
import {MAT_DIALOG_DATA} from '#angular/material/dialog';
import { MatDialogRef} from "#angular/material/dialog";
#Component({
selector: 'app-needs-dialog',
templateUrl: './needs-dialog.component.html',
styleUrls: ['./needs-dialog.component.css']
})
export class NeedsDialogComponent implements OnInit {
needs!: String[];
constructor( private dialogRef: MatDialogRef<NeedsDialogComponent>, #Inject(MAT_DIALOG_DATA) data)
{
console.log("logging data:")
console.log(data);
this.needs=data
console.log("logging needs array in NeedsDialogComponent:");
console.log(this.needs);
}
ngOnInit(): void {
}
close() {
this.dialogRef.close();
}
}
needs-dialog.component.html
<h2 mat-dialog-title>Szükségletek</h2>
<mat-dialog-content >
<ul *ngFor="let value of needs; index as i" >
<li>{{needs}}</li>
</ul>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>Cancel</button>
</mat-dialog-actions>
**opening matdialog with method: **
openDialog(dogid:any): void {
this.matdialog.open(NeedsDialogComponent, {data:this.getDogNeeds(Number(dogid)),width: '500px',
height: '500px'});
}
console output from dialog logging
dialog window

By your logging, the data is a 2D array with the list you want as the first element.
this.needs = data[0];
should work, but it seems like either your getDogNeeds() function or your back end is returning data in the wrong format.

Related

Display values from API In Angular 6 page

I am completely new to frontend dev and trying to display API data in an Angular 6 application and can't figure out how to do it.
I can display values in the top level of the returned details but it's the sub level details I am struggling with.
I am using an Angular 6 app using Routing.
Below is all my code
Homepage.component.html
<h2>Book ID List</h2>
<button (click)="getBooks()">Get</button>
<div *ngFor="let book of books.items">
<p>ID: {{book.id}}</p>
</div>
I can get the 'ID'
I am using a service to get the data from the test API
Service.component.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class ApiServiceService {
url = 'https://www.googleapis.com/books/v1/volumes?q=HTML5 Wire-frames';
constructor(private http: HttpClient) { }
private extractData(res: Response) {
const body = res;
return body || {};
}
getBooks(): Observable<any> {
return this.http.get(this.url).pipe(
map(this.extractData));
}
}
Homepage.component.ts
import { Component, OnInit } from '#angular/core';
import { ApiServiceService } from '../../services/api-service.service';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
books: any = [];
constructor(private apiService: ApiServiceService) { }
ngOnInit() { }
getBooks() {
this.books = [];
this.apiService.getBooks().subscribe((data: {}) => {
console.log(data);
this.books = data;
});
}
}
At present this return the following:
What I want to do is display the value from the 'eBook' which is under the 'saleInfo' level. I know I need to the change the loop for each array returned in the HTML but this is where I'm stuck.
Also I'm not sure the code I have is the best, but it's working. As I said I'm new to all this, and can pull values from top level but not sublevels in the API.
I would recommend better naming for your service, Service.compoent.ts isn't ideal, api.service.ts is much more understandable.
Also you can see that when you subscribe, you are using data: {}, this means that the function should expect a value of type Object, but I would use any, since you use Observable<any>
Now for the problem.
I have created stackblitz which does just what you wanted. I think you have got confused with the comments. You don't want to change let book of books.items to let book of books because you would be iterating over object, which you cannot do in *ngFor.
Change the line this.books = data; to this.books.push(data);
Since, if it is this.books = data; and because the books is of type any. It will accept anything. So, now after this line, this.books = data; it becomes object which contains value of data variable. So, you should use,
this.books.push(data);
To make it behave like an array too. Then, you can access books with *ngFor.
So, now in the HTML you can access via *ngFor as:
<div *ngFor="let book of books">
<div *ngFor="let items of book.items">
<p>ID: {{items.id}}</p>
<p>ebook: {{items.saleInfo.isEbook}}</p>
</div>
</div>

Child component cant get array from its parent after removing an element.

I'm a beginner to angular. I've got small ap with 3 components.
One component is just input and button which sends input value to the parent, and parent adds the incoming input to an array which is send forward to child where I want to print out all objects of array. Whenever I run function remove() and try to add another element afterwards by add() it is added only to listOfTasks but it's not added to taskList. Can someone explain why?
Component with input:
export class InputTaskComponent implements OnInit {
#Output('newTask') newTask = new EventEmitter<string>();
input: string;
constructor() { }
ngOnInit() {
}
add() {
this.newTask.emit(this.input);
this.input='';
}
Main component:
export class AppComponent {
addedTask: string;
listOfTasks: string[]=[];
doneTask:string[]=[];
constructor() {
}
receiveNewTask(event) {
this.addedTask=event;
this.listOfTasks.push(this.addedTask);
}
receiveDoneTask(event) {
this.doneTask.push(event);
}
}
Second child:
export class AddTaskComponent implements OnInit {
#Input('tasksFromInput') taskList: string[];
#Output('doneTask') doneTask = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
done(task) {
this.doneTask.emit(task);
}
remove(task) {
this.taskList = this.taskList.filter(e => e !== task);
console.log(this.taskList);
}
HTML of main component:
<div>
<div style="float:left; width:300px;">
<app-input-task (newTask)="receiveNewTask($event)">
</app-input-task>
</div>
<div style="float:left; width:300px;">
<app-add-task [tasksFromInput]="listOfTasks" (doneTask)="receiveDoneTask($event)">
</app-add-task>
</div>
<div style="float:left; width:300px;">
<app-done-task [done]="doneTask">
</app-done-task>
</div>
</div>
This is due to how change detection works in Angular. You have arrays in several places and use them as inputs for components.
When you add a task to them you use the push method which adds the element to the array, but the array itself is the same one, basically the reference does not change.
When you want to add an object to the tasks list and trigger the change detection you have to create a new array, for example:
this.listOfTasks = [...this.listOfTasks, this.addedTask];
In this way the example app will work. More info about change detection in Angular here.

Angular 2: How retrieve the changes that are done to an injected html?

I've inject HTML from an object that I created and than using a pipe I by pass the angular security to display input fields, text area etc..
I am inject an input field, some text and a <p contenteditable="true"> change me </p> that you can edit.
3) How to than track changes when user is updating the inner of and push it back to the object ?
If the user changes the HTML inside [innerHTML]="item.htmldata" is there a way to track it?
obj: Array<any> = [
{ htmldata: '<div> <strong> There should be a input field below </strong> <input type="text" value="search" /></div>' },
{ htmldata: '<div> <strong> me to, how are you </strong></div>'}
]
I am running through an ngFor loop and binding the data through angular [innerHTML]="item.htmldata"
<div *ngFor="let item of obj" >
{{item.htmldata | json }}
<div [innerHTML]="item.htmldata" | safeHtml></div>
<br />
<hr>
</div>
**SafeHTML pipe**
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeResourceUrl, SafeUrl} from '#angular/platform-browser';
DomSanitizer
#Pipe({name: 'safeHtml'})
export class Safe {
constructor(private sanitizer: DomSanitizer){}
transform(style) {
return this.sanitizer.bypassSecurityTrustHtml(style);
//return this.sanitizer.bypassSecurityTrustStyle(style);
// return this.sanitizer.bypassSecurityTrustXxx(style); - see docs
}
}
Plunker: http://plnkr.co/edit/ADeAEz81a07Cl2yrDGqQ?p=preview
Replace your sanitizedHtmlProperty by this code.
Sample Code
public get sanitizedHtmlProperty() : SafeHtml {
return this._sanitizer.bypassSecurityTrustHtml(this._originalHtmlProperty);
}
Plunker
Edit
You can use Blur and keyup
import {Directive, ElementRef, Input, Output, EventEmitter, OnChanges} from "#angular/core";
#Directive({
selector: '[contenteditableModel]',
host: {
'(blur)': 'onEdit()',
'(keyup)': 'onEdit()'
}
})
export class ContentEditableDirective implements OnChanges {
#Input('contenteditableModel') model: any;
#Output('contenteditableModelChange') update = new EventEmitter();
constructor(
private elementRef: ElementRef
) {
console.log('ContentEditableDirective.constructor');
}
ngOnChanges(changes) {
console.log('ContentEditableDirective.ngOnChanges');
console.log(changes);
if (changes.model.isFirstChange())
this.refreshView();
}
onEdit() {
console.log('ContentEditableDirective.onEdit');
var value = this.elementRef.nativeElement.innerText
this.update.emit(value)
}
private refreshView() {
console.log('ContentEditableDirective.refreshView');
this.elementRef.nativeElement.textContent = this.model
}
}
Reference
Edit 2
Here is code for tracking changes Plunker

Angular2 Observable, interating through object in service gives errors

I've looked on the board and on many sites but can't find a solution to my issue.
The issue has already been discussed but nothing seems to work on my code.
So! First, the JSON produced by my middleware is like so:
{
"uuid": "5c5260ec-5bcd-451a-ad68-57eb9572c185",
"latitude": 41,
"longitude": 1,
"temoin": {
"numeroDeTelephone": 342391,
"nom": "bruce",
"prenom": "wayne",
"sexe": "m",
"age": 12,
"taille": 150,
"poids": 62,
"groupeSanguin": "a+"
}
}
As you can see, I have two objects (which are described in my Angular app) the main object is signalement which contains a temoin object.
Signalement:
import { TemoinObjet } from './temoin.objet';
export class Signalement{
public uuid: String;
public latitude: any;
public longitude: any;
public temoin: TemoinObjet;
}
Temoin:
export class TemoinObjet{
public telephone: Number;
public prenom: String;
public nom: String;
public sexe: String;
public age: Number;
public taille: Number;
public poids: Number;
public groupeSanguin: String;
}
I switched from promises in the component to a service intended to get data:
import { Http, Response } from '#angular/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
import { Signalement } from '../domain/objets/signalement.objet';
import { TemoinObjet } from '../domain/objets/temoin.objet';
#Injectable()
export class SignalementService{
private urlRef: string = 'http://localhost:8080/Asklepios-1.0/ws/signalement';
constructor(private http: Http) {
}
recupererSignalements():Observable<Signalement[]>{
return this.http.get(this.urlRef).map((res:Response) => res.json());
}
get(uuidARetrouver: String, liste:any) : Signalement{
return liste.find(s => s.uuid === uuidARetrouver);
}
}
I'm using the recupererSignalements() method which returns an Observable.
In my component I've created a method with the same name and called it in the ngOnInit. Here's the full component:
import { Component, OnInit } from '#angular/core';
import { Signalement } from './domain/objets/signalement.objet';
import { SignalementService } from './services/signalement.service';
import { TemoinObjet } from './domain/objets/temoin.objet';
import { Observable } from 'rxjs/Rx';
#Component({
selector: 'liste-signalement',
templateUrl: './list_signalement.html',
providers: [SignalementService]
})
export class ListSignalementsComponent implements OnInit {
signalements: Signalement[];
constructor(private signalementService: SignalementService){
}
ngOnInit(){
this.recupererSignalements();
console.log(this.signalements);
}
recupererSignalements(){
this.signalementService.recupererSignalements().subscribe(donnees => this.signalements = donnees, ()=>console.log("Fail"), ()=>console.log("Done : "+this.signalements));
}
}
Once it's done I want to iterate over the signalements array to show the information in my view, but everytime I get the error Error: Error trying to diff '[object Object]' at DefaultIterableDiffer.diff. When using *ngFor I instead see:
Error: Cannot find a differ supporting object '[object Object]' of type
'object'. NgFor only supports binding to Iterables such as Arrays. at
NgForOf.ngOnChanges.
Here's the view :
<ul>
<li *ngFor="let signalement of signalements">
{{signalement.uuid}}
</li>
</ul>
I've tried the asyncpipe. Using this I get no more errors but can't see anything in my view.
The issue here is that ngFor expects an array – although it's the equivalent of ng-repeat in Angular 2+ world, it was designed to only operate on arrays which removes the need to account for the many edge cases ng-repeat covered, including iterating over non-iterables (such as objects, in your case).
If you're just looking to render a list of Signalements, then the one Signalement just needs to be inside an array. If you're trying to iterate over the properties of one Signalement, you would need to write a pipe that converts an object into an array of keys, such as the "keys" pipe in the 'ngx-pipes' library.

Add Property to my NPM Module

This might be answered, but damn if I can find it . I am creating a module, and its working, the issue is I want to assign a property to another property on my module.
so in angular 2 (with ng-module) I have created a simple panel
<simple-panel name="MyPanel"></simple-panel>
I have it working great, the issue is I want to assign a property to the name Property now and I have no idea what is the best way to do this.
so I would like to return {{MyPanel.thisProperty}} for use on the page where I am calling the tag.
here is a sample of what I am doing, stripped down for this question
here is my simple-panel.ts
import {Component,NgModule,ModuleWithProviders, Directive, Input} from '#angular/core';
/**
* Content of the edit panel.
*/
#Directive({
selector: 'simple-panel-edit-panel-content'
})
export class SimplePanelEditPanelContent {}
#Component({
selector: 'simple-panel',
templateUrl: 'simple-panel.html',
styleUrls: ['simple-panel.css'],
encapsulation: ViewEncapsulation.None
})
export class SimplePanel{
private _name: string;
private _announceedit: boolean = false;
private _buttonname: string = 'edit';
/** This sets the Name as a variable that can be used. */
#Input()
get name(): string { return this._name; }
set name(value) { this._name = value; }
/**Sets the Edit Announcement */
#Input()
get editannounce(): boolean { return this._announceedit; }
set editannounce(value: boolean) {
if (!value) {
this._announceedit = true;
this._buttonname = 'search';
}else{
this._announceedit = false;
this._buttonname = 'edit';
}
}
}
#NgModule({
exports: [SimplePanel,SimplePanelEditPanelContent],
declarations: [SimplePanel,SimplePanelEditPanelContent],
})
export class SimplePanelComponent {
static forRoot(): ModuleWithProviders {
return {
ngModule: SimplePanelComponent,
providers: []
};
}
}
here is the simple-panel.html
<md-card>
<md-card-title-group>
<button md-raised-button (click)="editannounce=editannounce;"><md-icon>{{ _buttonname }}</md-icon></button>
</md-card-title-group>
<md-card-content>
<ng-content select="simple-panel-edit-panel-content"></ng-content>
</md-card-content>
<md-card-actions>
<button md-raised-button (click)="editannounce = editannounce"><md-icon>save</md-icon> SAVE</button>
</md-card-actions>
</md-card>
when someone uses the module, a panel is created with a button
when someone clicks the button I can access the variable within the template above, but what I want to do is actually access a variable that is used on the page itself where they call the module to use. it would be nice to have it named MyPanel.announceedit or MyPanel.editable as an example, but the main thing is that a variable is created, and watched, when it changes it passes it back up to where the module is bieng used and allows user the ability to access it within the content area, so if they added an input and wanted to see if the button was clicked to set the readOnly attribute they could. Hopefully this makes more sense.
If you write it like
<simple-panel [name]="MyPanel"></simple-panel>
in the component that includes this html, you can access/set MyPanel with a simple this.MyPanel.
And in your SimplePanel component
#Input() name;
...
this.name = "something";
is again all you need to set and get that field.

Resources