Dynamic array with angular - arrays

Basically I have an api that returns data when I do a search, I push them into an array and I display them by doing an ngFor in my html.
When I want to do a new search it's the same function that is called, but the html is not updated while I get new data.
It always appears the old data recovered the first time.
To search, i used this code :
SearchBar.component.ts
export class RechercheToutComponent implements OnInit {
searchInput = new FormControl('');
constructor(
private router: Router,
private recherche: RechercheComponent
) { }
ngOnInit(): void {
}
search() {
if(this.router.url.match(/recherche.*!/)){
this.recherche.searchResult(this.searchInput.value)
}else{
this.router.navigate(['/recherche'], {queryParams: {search: this.searchInput.value}}).then(r => this.recherche.searchResult(this.searchInput.value))
}
}
}
SearBar.component.html
<form class="catalogue-search-form" (ngSubmit)="search()">
<div class="search-bar">
<input type="text"
[formControl]="searchInput"
placeholder="Rechercher dans Intra"
/>
<button type="submit" class="text-button">
<mat-icon>search</mat-icon>
</button>
</div>
</form>
Search.component.ts
export class RechercheComponent implements OnInit {
searchParam: any;
results$: Observable<Array<any>>;
isResultLoading: boolean = true;
constructor(
private route: ActivatedRoute,
private http: HttpClient
) {
}
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
this.searchParam = params['search']
});
this.searchResult(this.searchParam);
}
searchResult(searchParam) {
this.http.get(`${environment.apiUrl}/Recherchetout.php`, {params: {search: searchParam}}).subscribe(
(data: Array<any>) => {
this.results$ = of(data)
this.isResultLoading = false;
}
);
}
}
Search.component.html
<div class="recherche">
<div class="spinner-search" *ngIf="isResultLoading">
<app-spinner></app-spinner>
</div>
<div class="content" *ngIf="!isResultLoading">
<div *ngFor="let oui of results$ | async">
<div *ngIf="oui.produit != undefined">
{{ oui.produit.pdf }}
</div>
</div>
</div>
</div>
I tried to create observables but it didn't work, with a simple array too.
So my question is: Why is my data not updating on my html?
And how to do it ?
Sorry in advance for the mistakes, or for the disastrous code I begin in angular

you are injecting RechercheComponent inside the SearchBar component, angular will create different instance than the one used on the UI.
to send data between multiple components create a parent component and use it to allow communication between the two components (use the Input and Output attributes).
make your system navigate to the parent then
Parent.Component.ts
export class ParentComponent {
data: any[];
isResultLoading = false;
updateData(data: any[]) {
this.data = data
}
}
parent.component.html
<app-search-bar (change)="dataChange($event)" [(isResultLoading)]="isResultLoading"></app-search-bar>
<app-search [data]="data" *ngIf="!isResultLoading"></app-search>
update your Search.component.ts
//add outputs and inputs
#Output() change = new EventEmitter<any[]>(); //make sure to import EventEmitter from #angular/core
#Input() isResultLoading : boolean
// update searchResult subscription function
searchResult(searchParam) {
this.isResultLoading = true;
this.http.get(`${environment.apiUrl}/Recherchetout.php`, { params: { search: searchParam } }).subscribe(
(data: Array<any>) => {
this.change.emmit(data);
this.isResultLoading = false;
}
);
}
and finally instead of having observable result$ inside Search.component.ts
replace it with #Input() data:Any[]

Assuming you want to refresh the search every time a URL change, update you ngOnInit by moving this.searchResult(this.searchParam); inside the subscribe method
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
this.searchParam = params['search']
this.searchResult(this.searchParam);
});
}
with this searchResult will be called every time the router parameters get change instead of updating for the first time ngOnInit get called

Related

ngFor not update view unless click on view Angular

I tried detectChanges(), marforCheck() as well, reassign but nothing changes. They just change after I click on view again.
Scenario:
I get data from store, I tracked data change successfully in subscribe function.
this.allInfo$.subscribe(data => {
this.allInfos = data;
});
<div (click)="addInfo(social)" *ngFor="let info of allInfos"
[ngClass]="{'active': info.connected}">
<span>{{info.name}} {{info.connected}}</span>
</div>
When I click addInfo, app will open thirdparty dialog like firebase login, if success will return new data and update to allInfo$ I tracked. But the view does not changes? Is that angular bug? View change if I click somewhere on screen
My angular version 7
UPDATED: My problem was resolved by using _ngZone.run()
You kept the click event on the , so when you are clicking on that div it is updating.
<div *ngFor="let info of allInfos"
[ngClass]="{'active': info.connected}">
<span (click)="addInfo(social)">{{info.name}} {{info.connected}}</span>
</div>
in this code the view will update when you click on the span
trackBy
<div (click)="addInfo(social)" *ngFor="let info of allInfos;trackBy:trackByFunction"
[ngClass]="{'active': info.connected}">
<span>{{info.name}} {{info.connected}}</span>
</div>
.ts
trackByFunction(index, info){
if(!info)return null;
return info.id; //or return index;
}
Link:-https://medium.com/better-programming/improving-angular-ngfor-performance-through-trackby-ae4cf943b878
a subscription not put a "listener" in the dbs, so any change in the dbs is reflex. Each change in the dbs, you need or get the list again or add manually to your array allInfos.
You say "When I click addInfo, app will open thirdparty dialog like firebase login, if success will return new data and update to allInfo$"
I have no idea about your addInfo, so I supouse your addInfo is like
addInfo(data)
{
this.service.addData(data).subscribe(res=>{
console.log(res) //generally success
})
}
You can so some like
addInfo(data)
{
this.service.addData(data).subscribe(res=>{
if (sucess)
this.allInfos.push(data)
})
}
or
addInfo(data)
{
this.service.addData(data).pipe(
switchMap(res=>{
return res.success?this.allInfo$:of(null)
})
).subscribe(res=>{
if (res)
this.allInfos=res
})
}
It's possible that the "thirdparty dialog make a next to your $allInfo", (than all of this it's unnecesary) but I don't know. Imagine that you has a typical CRUD service like
#Injectable()
export class ProductService {
endpoint: string = '[YOUR_DB_SERVER_IP]';
constructor(
private http: HttpClient
) {}
// CREATE
createProduct(productToCreate: IProduct): Observable<IProduct[]> {
return this.http.post(`${this.endpoint}/products`, productToCreate);
}
// READ
readProducts(): Observable<IProduct[]> {
return this.http.get(`${this.endpoint}/products`);
}
// UPDATE
updateProduct(objToUpdate: IProduct): Observable<IProduct[]> {
return this.http.patch(`${this.endpoint}/products/${objToUpdate.id}`, objToUpdate};
}
// DELETE
deleteProduct(): Observable<IProduct[]> {
return this.http.delete(`${this.endpoint}/products/${objToDelete.id}`);
}
}
We are changing so, each change emit an event
private dbsChangeSubject = new Subject<any>();
dbsChange = this.dbsChangeSubject .asObservable();
// CREATE
createProduct(productToCreate: IProduct): Observable<IProduct[]> {
return this.http.post(`${this.endpoint}/products`, productToCreate).pipe(
tap(result=>{
if (result.success)
this.dbsChangeSubject.next(true)
}))
);
}
// UPDATE
updateProduct(objToUpdate: IProduct): Observable<IProduct[]> {
return this.http.patch(`${this.endpoint}/products/${objToUpdate.id}`, objToUpdate.pipe(
tap(result=>{
if (result.success)
this.dbsChangeSubject.next(true)
}))
);;
}
// DELETE
deleteProduct(): Observable<IProduct[]> {
return this.http.delete(`${this.endpoint}/products/${objToDelete.id}`).pipe(
tap(result=>{
if (result.success)
this.dbsChangeSubject.next(true)
}))
);
}
}
Know we can subscribe to
this.service.dbsChange().pipe(
startWith(true),
switchMap(()=>{
this.service.readProducts()
})).subscribe(res=>{
this.products=res
})
Here we subscribe to "dbsChange", not to readProducts(), so we are listen to all the this.dbsChangeSubject.next that we has
As dlam said in his edited question, using NgZone in the new window solves the problem.
Example:
import { NgZone} from '#angular/core';
export class NewWindowComponent implements OnInit{
constructor(private ngZone: NgZone) { }
ngOnInit() {
this.someService.getChangedData.subscribe(data => {
this.ngZone.run(() => {
this.someData= data;
});
});
}
}

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 component view updated continuously

I have an Angular 2 component that displays a list of Items, and that registers to a service that publishes events. The problem is that even if I don't do anything when I receive an event, Angular updates the view (or at least do something when, I guess, it shouldn't).
Here is a plunker.
As you can see in the console, the "getTitle()" method of my items is called each time my service publishes a message.
Even if I don't register to my service and if my component doesn't implement the MyServiceListener interface, getTitle is called each time the service gets a message. If I don't give the service to my component in its constructor, everything is fine. So, something seems wrong with my dependency injection, but what?
Here is the relevant code of the plunker:
My service and its listeners interface:
export interface MyServiceListener {
onMessage(_message: any);
}
export class MyService {
private m_listener: MyServiceListener;
constructor() {
window.setInterval(() => {
if (this.m_listener !== undefined) {
this.m_listener.onMessage("Hi");
}
}, 500);
}
setListener(_listener: MyServiceListener) { this.m_listener = _listener; }
}
The Item class:
export class Item {
m_title: string;
constructor(_title: string) {
this.m_title = _title;
}
getTitle(): string { console.log("getTitle"); return this.m_title; }
}
My component:
#Component({
selector: 'my-app',
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
export class App implements TestBugAngularServiceListener {
private m_items: Array<Item> = new Array<Item>();
constructor(_communicationService: MyService) {
this.m_items.push(new Item("A"));
this.m_items.push(new Item("B"));
this.m_items.push(new Item("C"));
_communicationService.setListener(this);
}
onMessage(_message: any) {
}
}
bootstrap(App, [MyService]).catch(err => console.error(err));
Both articles : Change detection and Angular immutability explain a lot of thing about how Angular 2 detect changes of object, and how the tree of components in angular 2 is traversed to perform data binding...
In your sample, I think your component "my-app" can be considered to be "Immutable", so changing its "change detection strategy" to OnPush solve your problem.
You can write this :
#Component({
selector: 'my-app',
changeDetection: ChangeDetectionStrategy.OnPush,
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
And after adding the import to ChangeDetectionStrategy, databinding of "my-app" will not be computed after each browser event, but only when its input change, so never...

Resources