Processing a two-dimensional array in Angular 7. ngFor - javascript

How to handle a two-dimensional array using ngFor?
I receive here such array
As a result, I need to get the blocks in which the data from the array is displayed in order. That is, in the case of an array that is represented on the screen, there would be 10 blocks.
Example:
<div>
<span>Yandex</span>
<span>Yandex.N.V....</span>
<span>https://en.wikipedia.org/wiki/Yandex</span>
</div>
<div>
<span>Yandex Browser</span>
<span>IPA:...</span>
<span>https://en.wikipedia.org/wiki/Yandex_Browser</span>
</div>
etc.
I do it that way.
<h3>Get Articles</h3>
<div>
<div *ngIf="articles">
<div *ngFor="let article of articles">
<span>{{ article[1] }}</span>
<span>{{ article[2] }}</span>
<span>{{ article[3] }}</span>
</div>
</div>
</div>
I understand that this is wrong, but I can not find my stupid mistake.
The output is either an error or a strange conclusion.
search.component.ts
import { Component, OnInit } from '#angular/core';
import { Article, ArticlesService } from '../../services/articles.service';
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css'],
providers: [ArticlesService]
})
export class SearchComponent implements OnInit {
constructor(private articlesServices: ArticlesService) { }
searchQuery: string;
limit: number;
error: any;
articles: { };
// noinspection JSMethodCanBeStatic
getUrl(searchQuery: string) {
return 'https://en.wikipedia.org/w/api.php?action=opensearch&search='
+ searchQuery + '&limit=10&namespace=0&format=json&origin=*';
}
showArticles() {
this.articlesServices.getArticles(this.getUrl(this.searchQuery))
.subscribe(
(data: Article) => this.articles = Object.values({
title: data[0],
collection: data[1],
description: data[2],
links: data[3]
}),
error => this.error = error
);
console.log(this.articles);
}
ngOnInit() {
}
}
article.component.ts
import { Component, OnInit, Input } from '#angular/core';
import {Article, ArticleInfo, ArticlesService} from '../../services/articles.service';
#Component({
selector: 'app-articles',
templateUrl: './articles.component.html',
styleUrls: ['./articles.component.css'],
})
export class ArticlesComponent implements OnInit {
#Input() articles: Article;
#Input() searchQuery: string;
constructor(private articlesServices: ArticlesService) { }
information: ArticleInfo;
getUrl(searchQuery: string) {
return 'https://ru.wikipedia.org/w/api.php?action=query&list=search&srsearch=' +
searchQuery + '&utf8=&format=json&origin=*';
}
showArticlesInformation() {
this.articlesServices.getArticlesInfo(this.getUrl(this.searchQuery))
.subscribe(
(data: ArticleInfo) => this.information = {
query: data.query.search
}
);
console.log(this.information);
}
ngOnInit() {
}
}
article.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
export interface Article {
title: string;
collection: string[];
description: string[];
links: string[];
}
export interface ArticleInfo {
query: {
search
};
}
#Injectable({
providedIn: 'root'
})
export class ArticlesService {
constructor(private http: HttpClient) { }
getArticles(url) {
return this.http.get(url)
.pipe(
retry(3),
catchError(this.handleError)
);
}
getArticlesInfo(url) {
return this.http.get<ArticleInfo>(url);
}
// noinspection JSMethodCanBeStatic
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
return throwError(
'Something bad happened; please try again later.');
}
}
Come 2D array
Then it should turn out like this

Try this,
<div>
{{articles[0]}}
</div>
<div *ngFor="let article of articles[1]; let i=index">
<span>
{{article}}
</span>
<span *ngFor="let info1 of articles[2]; let j=index" [hidden]="i!=j">
{{info1}}
</span>
<span *ngFor="let info2 of articles[3]; let k=index" [hidden]="i!=k">
{{info2}}
</span>
</div>

Try storing the result into Observable and into the html file use async pipe.
<div *ngFor="let article of articles | async">
In your search.component.ts
articles : Observable<Article>;
...
this.articles = this.articlesServices.getArticles(this.getUrl(this.searchQuery)).catch(error => this.error = error );

Related

How to set default value in RxJs with BehaviorSubject

I wonder how to set default value in RxJs with BehaviorSubject, so I would like to have default value 15 images. From my interface I would like take only 15 urls, and how to subscibe them. I would be very grateful if someone could explain to me how to make it work.
app component
import { Component, OnInit } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
import { DogInfo } from './interface/dogInfo';
import { HttpService } from './service/http.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
constructor(private httpService: HttpService) { }
items$: any = new BehaviorSubject<DogInfo[]>();
ngOnInit() {
this.items$.subscribe((item: any) => this.httpService.fetchDogsFromApi());
}
}
interface
export interface DogInfo{
id: number;
name?: string;
breadGroup?: string;
lifeSpan?: string;
breadFor?: string;
temperament?: string[];
url: string;
}
You can use like below code:
items$: any = new BehaviorSubject<DogInfo[]>([{id: 0, url: 'your-url'}]);
Probably there is a better way, you could use take and grab the first 15 items from dogs, and set that value to your custom Subject as:
items$ = new BehaviorSubject<DogInfo[]>([]); // initialize as an empty array
private dogsArray: DogInfo[] = [];
constructor(private http: HttpService) {}
ngOnInit(): void {
this.http.fetchDogsFromApi().pipe(
take(15),
tap((dog) => this.dogsArray.push(dog))
).subscribe({
// after the 15 dogs are taken, it will emit that data to the template
complete: () => this.items$.next(this.dogsArray)
});
}
HTML
<ng-container *ngIf="(items$ | async) as dogs">
<div *ngFor="let dog of dogs; let i = index">
<p>Dog #: {{ i }}</p>
<p>Name: {{ dog.name }}</p>
<p>...</p>
</div>
</ng-container>

Angular removing elements from a list shared with a service

this problem is driving me crazy.
I have an array defined within a service, which is used in 3 other components:
This is the service, file products.service.ts (notice the product array of Products)
import { Injectable } from '#angular/core';
import { ​​HttpClient } from '#angular/common/http';
import { Product } from './../models/Product';
import { ProductForm, productFormToProduct } from './../models/ProductForm';
// #Injectable({
// providedIn: 'root'
// })
const apiUrl = 'http://localhost:3000/products';
#Injectable()
export class ProductsService {
public products: Product[] = [];
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get(apiUrl)
}
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
return this.http.delete(apiUrl + "/" + p.id)
}
storeNewProduct(pf: ProductForm) {
const idList = this.products.map((x) => {return x.id});
const i = Math.max(...idList) + 1;
const p = productFormToProduct(pf);
p.id = i;
this.products.push(p);
return this.http.post(apiUrl, p)
}
}
This is the component where i subscribe to getProducts, and fill the array (file products.component.ts):
import { Component, OnInit } from '#angular/core';
import { ProductsService } from '../../shared/services/products.service';
import { Product } from '../../shared/models/Product';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
}
}
And this is the component where i subscribe to deleteProduct (file product-card.component.ts):
import { Component, Input, OnInit } from '#angular/core';
import { ProductsService } from '../../services/products.service';
import { Product } from './../../models/Product';
#Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent implements OnInit {
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
}
#Input() product: Product
public buttonDeleteFunction() {
this.productsService.deleteProduct(this.product).subscribe();
}
}
The problem is, when i click on some delete product button, i have this weird behaviour:
Before click:
After click:
Here is the products.component.html file:
<div class="products__header">
<h3 class="products__heading">
Listado de productos ({{ products.length }})
</h3>
<input
class="products__search"
placeholder="Buscador"
type="search"
[(ngModel)]="searchText"
/>
</div>
<p *ngFor="let p of products">{{ p.name }}</p>
<p>{{ products }}</p>
<div class="products__list">
<app-product-card
*ngFor="let p of products | filterNames: searchText"
[product]="p"
></app-product-card>
</div>
Why do i get the expected behaviour in only two of the four places where i use the products list?
I know i can use an Output to manually remove the item from the list when i click the button, but i have been told that services are used instead of Inputs/Outputs when i want to share between multiple components, so i'd rather not use an Output for this
When you use your approach with common data on service layer then a common pitfall is that Angular does not detect the changes that affect your component. In that case you must inform your component for those changes using an emmiter.
Use an emmiter on service
productUpdated :EventEmitter = new EventEmitter();
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
this.productUpdated.emit(this.products);
return this.http.delete(apiUrl + "/" + p.id)
}
And then listen for that change ProductsComponent
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
this.productsService.productUpdated.subscribe( (data) => {
this.products = data;
});
}

How to get keys from firebase database [duplicate]

The question has been answered but I'm looking for a, um, more straightforward one if available. It seems strange that we'd have to implement not one but two mappings just to have access to the object keys.
basic firebase db:
As can be seen, the course objects clearly have keys.
Mark-up:
<ul>
<li *ngFor="let course of courses$ | async">
<b>Key:</b> {{course.$key}} <!-- doesn't show --!>
<b>Title:</b> {{course.Title}}
<b>Duration:</b> {{course.Duration}}
<b>Author:</b> {{course.Author}}
<p><button (click)="deleteCourse(course)">Remove</button></p>
<hr>
</li>
</ul>
Now, the courses display just fine, but I don't know how to get a reference to the key in order to delete it. (Or perhaps I'm not using the right method on my firebaseDatabase Object). Either way, when I log the key in the console, it shows as undefined.
export class AppComponent {
courses;
courses$: AngularFireList<any>;
constructor(private db: AngularFireDatabase) {
this.courses = db.list('/courses');
this.courses$ = this.courses.valueChanges();
}
...
deleteCourse(course) {
console.log(course.$key); // -> undefined
this.db.object('/courses/' + course.$key).remove();
}
}
Updated Answer
Rxjs have changed how it pipes data. now you have to use .pipe().
this.courses$ = this.courses.snapshotChanges().pipe(
map(changes =>
changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
)
);
Original Answer
.valueChanges() contain simply data, no key with it. you need to use .snapshotChanges()
this.courses$ = this.courses.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
});
now just use {{course.key}}
here is your corrected code
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
export class AppComponent {
courseRef: AngularFireList<any>;
courses$: Observable<any[]>;
constructor(private db: AngularFireDatabase) {
this.courseRef = db.list('/courses');
this.courses$ = this.courseRef.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val()
}));
});
}
...
deleteCourse(course) {
console.log(course.key);
this.db.object('/courses/' + course.key).remove();
}
}
to create an interface:
export interface Client{
key?: string;
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
balance?:number;
}
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireList, AngularFireObject} from '#angular/fire/database';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable()
export class ClientService {
client: AngularFireList<any>;
clients: Observable<any[]>;
constructor(public db: AngularFireDatabase) {
this.client = db.list('/clients');
this.clients = this.client.snapshotChanges().pipe(
map(res => res.map(c => ({ key: c.payload.key, ...c.payload.val()
}))
));
}
getClients(){
return this.clients;
}
}
import { Component, OnInit } from '#angular/core';
import { ClientService } from '../../services/client.service';
import { Client} from '../../models/client'
#Component({
selector: 'app-clients',
templateUrl: './clients.component.html',
styleUrls: ['./clients.component.css']
})
export class ClientsComponent implements OnInit {
clients:Client[];
constructor(
public clientService:ClientService
) { }
ngOnInit(){
this.clientService.getClients().subscribe(clients=>{
this.clients = clients;
console.log(this.clients);
})
}
}

Passing data between components using a service Angular

There are many examples around the web on this subject but none of them helped me. This is the scenario: I've got 2 components and a service. The two components aren't parent/children but are 2 independent components. One of them has a list of names, the other should load a table when one of the names is clicked. This is my home.html with both components
<div class="material-docs-app">
<div class="docs-primary-header">
<h1>Yep!</h1>
</div>
<div fxLayout="row" fxLayout.xs="column" class="component-layout-body">
<app-heroes-sidenav></app-heroes-sidenav>
<app-heroes-table #heroesTable fxFlex="1 2 calc(15em + 20px)" style="width: 100%"></app-heroes-table>
</div>
</div>
Heroes sidenav component:
<div *ngIf="loadingData == true">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<nav *ngIf="loadingData == false">
<p *ngFor="let item of heroesNames.results let i = index" [attr.data-index]="i">
<button mat-button (click)="getHero(i)">
{{item.name}}
</button>
</p>
</nav>
On click getHero() is called correctly. This is the sidenav component ts:
import { Component, OnInit, Input } from '#angular/core';
import {SwCharactersServiceService} from '../sw-characters-service.service';
import {HeroesTableComponent} from '../heroes-table/heroes-table.component';
#Component({
selector: 'app-heroes-sidenav',
templateUrl: './heroes-sidenav.component.html',
styleUrls: ['./heroes-sidenav.component.css']
})
export class HeroesSidenavComponent implements OnInit {
heroesNames: any;
heroData:any;
loadingData = true;
#Input() heroesTable: HeroesTableComponent;
constructor(private _swService: SwCharactersServiceService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes() {
this._swService.getCharacters().then(result => {
this.loadingData = false;
this.heroesNames = result;
});
}
getHero(index) {
this._swService.getHero(index);
}
}
and this is the service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import 'rxjs/add/operator/map'
import {Observable} from 'rxjs/Observable';
#Injectable({
providedIn: 'root'
})
export class SwCharactersServiceService {
param:any;
constructor(private http: HttpClient) { }
getCharacters(): Promise<any[]> {
return this.http.get<any[]>("https://swapi.co/api/people/")
.toPromise()
.then(result => result)
.catch(this.handleError);
}
getHero(index): Observable<any>{
console.log(index);
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
return this.http.get("https://swapi.co/api/people/" + index, {
headers: headers
}).map(res => res );
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
I can correctly see the console.log(index) but the request doesn't work. There is no request initiated in chrome console network tab.
This is the component with the table:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import {SwCharactersServiceService} from '../sw-characters-service.service';
#Component({
selector: 'app-heroes-table',
templateUrl: './heroes-table.component.html',
styleUrls: ['./heroes-table.component.css']
})
export class HeroesTableComponent implements OnInit {
loadingData = true;
heroData :any;
subscription: Subscription;
constructor(private _swService: SwCharactersServiceService) {
this.subscription = this._swService.getHero(1).subscribe(result => { this.heroData = result; });
console.log(this.heroData);
}
ngOnInit() {
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
There are 2 problems now:
1) As you can see I wrote this._swService.getHero(1) without passing a dynamic param. How does it work? How can I pass the correct index?
2) The service doesn't fire and I haven't got any result.
Is there any other way to do that?
Thanks.
you can use BehaviourSubject to pass the index value and send the query request as the list is cliked
in the service
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
public index: BehaviorSubject<number> = new BehaviorSubject<number>(null);
in the sidenav component
getHero(index) {
this._swService.index.next(index);
}
in the hero table component
ngAfterViewInit(){
this._swService.index.subscribe(index=>{
if(index){
this._swService.getHero(index).subscribe(result => { this.heroData = result; });
}
})
}
You missed to subscribe to _swService.getHero(). If not subscribed to a method which returns an Observable, then it wont be invoked.
getHero(index) {
this._swService.getHero(index).subscribe(
(resp) => {
// manipulate your response here
console.log(resp);
},
(err) => {}
);
}

Invalid argument for pipe 'AsyncPipe'

So i get this when i try to get my data from firebase
Invalid argument '{"-KCO4lKzEJPRq0QgkfHO":{"description":"teste","id":1457488598401,"resourceUrl":"tete","title":"test2"}}' for pipe 'AsyncPipe' in [listItems | async in ArcListComponent#2:10]
ArcListComponent
import { Component, OnInit } from "angular2/core";
import { FirebaseService } from "../shared/firebase.service";
import { ArcItem } from "./arc-item.model";
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async " class="arc-item">
<h3>{{ item.name}}</h3><a [href]="item.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.description }}</blockquote>
<hr>
</li>
</ul>
`
})
export class ArcListComponent implements OnInit {
listItems: string;
constructor(private _firebaseService: FirebaseService) {}
ngOnInit(): any {
this._firebaseService.getResources().subscribe(
resource => this.listItems = JSON.stringify(resource),
error => console.log(error)
);
}
}
firebase_service
import { Injectable } from "angular2/core";
import { Http } from "angular2/http";
import "rxjs/Rx";
#Injectable()
export class FirebaseService {
constructor(private _http: Http) {}
setResource(id: number, title: string, description: string, resourceUrl: string) {
const body = JSON.stringify({ id: id, title: title, description: description, resourceUrl: resourceUrl});
return this._http
.post("https://######.firebaseio.com/resource.json", body)
.map(response => response.json());
}
getResources() {
return this._http
.get("https://######.firebaseio.com/resource.json")
.map(response => response.json());
}
}
I know i am trying to show my data the wrong way but i do not know how to fix this. any help appreciated.
The async pipe expects an observable or a promise. http.get and map operator return observable, so you can set the returned object into the listItems property of your component. You don't need to subscribe in this case:
this.listItems = this._firebaseService.getResources();
Moreover the object, this element will "receive" must be an array to be able to use it within an ngFor. You service returns an object and not an array from Firebase. If you want to iterate over the keys of the object, you need to implement a custom pipe:
#Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push({key: key, value: value[key]);
}
return keys;
}
}
and use it like this:
#Component({
selector: "arc-list",
template: `
<ul class="arc-list">
<li *ngFor="#item of listItems | async | keys" class="arc-item">
<h3>{{ item.value.name}}</h3><a [href]="item.value.resourceUrl" target="_blank" class="btn btn-success pull-right"><span>Go</span></a>
<hr>
<blockquote>{{ item.value.description }}</blockquote>
<hr>
</li>
</ul>
`,
pipes: [ KeysPipe ]
})
See this question for more details:
How to display json object using *ngFor
async pipe works with observables and/or promises. It does subscription for you, so you just have to pass an observable without subscribing to it in your code:
ngOnInit(): any {
this.listItems = this._firebaseService.getResources()
}

Categories