I know this question has been asked several times, but problem is that nobody tried to make a some fiddle or show results of code. This is what i have, i need to update values in other component based on value in some other component, but that is not just value,I have call function again in some other component.
I have some component that goes to database and update values, on second hand I have other component that read those values from database from service.
This is example of my code
tasks.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { environment } from '../../environments/environment';
import { Tasks } from './tasks';
#Injectable()
export class TasksProvider {
constructor(private http: HttpClient) { }
createNewTask(name: Name) : Observable<any> {
return this.http.post(environment.apiUri + 'tasks', { name, finished: false },
{ responseType: 'text' });
}
updateTask(id: Id, name: Name, finished: boolean) : Observable<any> {
return this.http.put(environment.apiUri + 'tasks/' + id, { name, finished },
{ responseType: 'text' });
}
getAllTasks(): Observable<Tasks[]> {
return this.http.get(environment.apiUri + 'tasks')
.map<any, Tasks[]>(data => data.map(Tasks.fromObject));
}
}
app.component.html
<app-tasks-list></app-tasks-list>
<app-tasks-add-new></app-tasks-add-new>
As you may see I have not child components, that is my main problem
tasks-list.component.ts
import {Component} from '#angular/core';
import { Tasks } from '../services/tasks';
import { TasksProvider } from '../services/tasks.service';
#Component({
selector: 'app-tasks-list',
templateUrl: './tasks-list.component.html',
styleUrls: ['./tasks-list.component.scss']
})
export class TasksListComponent {
tasks: Array<Tasks>;
constructor(private tasksProvider: TasksProvider) { }
ngOnInit() {
this.getTasksList();
}
displayedColumns: string[] = ['id', 'name', 'finished'];
private getTasksList() {
this.tasksProvider.getAllTasks()
.subscribe(tasks => {
this.tasks = tasks;
});
}
public updateCheckboxValue(id: number, name: string, event: any){
this.tasksProvider.updateTask(id, name, event.checked).subscribe(
result => {},
() => {
alert('Something went wrong');
})
}
}
tasks-add-new.component.ts
import { Component, OnInit, Inject } from '#angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { Tasks } from '../services/tasks';
import { TasksProvider } from '../services/tasks.service';
export interface DialogData {
name: string;
}
#Component({
selector: 'app-tasks-add-new',
templateUrl: './tasks-add-new.component.html',
styleUrls: ['./tasks-add-new.component.scss']
})
export class TasksAddNewComponent implements OnInit {
ngOnInit() {
}
constructor(public dialog: MatDialog, private tasksProvider: TasksProvider) {}
openDialog(): void {
const dialogRef = this.dialog.open(TasksAddNewDialog, {
width: '250px',
data: {name: this.animal}
});
dialogRef.afterClosed().subscribe(result => {
this.name = result
this.tasksProvider.createNewTask(this.name).subscribe(
result => {},
() => {
alert('Something went wrong');
})
}
}
}
#Component({
selector: 'tasks-add-new-dialog',
templateUrl: 'tasks-add-new-dialog.html'
})
export class TasksAddNewDialog {
constructor(
public dialogRef: MatDialogRef<TasksAddNewDialog>,
#Inject(MAT_DIALOG_DATA) public data: DialogData) {}
onNoClick(): void {
this.dialogRef.close();
}
}
You see now when i call function in tasks-add-new.component.ts like
this.tasksProvider.createNewTask(this.name).subscribe(
result => {},
() => {
alert('Something went wrong');
})
I need to call again function in tasks-list.component.ts
private getTasksList() {
this.tasksProvider.getAllTasks()
.subscribe(tasks => {
this.tasks = tasks;
});
}
Does any body have idea how i can do that the best practice?
On of the possible approach is to use Subjects.
1) Store task list on the service and provide subscribable Subject
private tasks: Array<Task>;
public $tasks: BehaviorSubject<Array<Task>>;
constructor(private http: HttpClient) {
this.$tasks = new BehaviorSubject([]);
...
}
getAllTasks() {
this.http.get(environment.apiUri + 'tasks')
.subscribe(data => {
this.tasks = data;
this.$tasks.next(this.tasks);
});
}
updateTask(params) {
this.http.post(/* params */).subscribe((task) => {
this.tasks = this.tasks.map(t => t.id !== task.id ? t : task);
this.$tasks.next(this.tasks);
});
}
createTask(...) {
// again, do a request, update this.tasks and call $tasks.next
...
}
2) Make one service Subject subscription on the component instead of multiple service methods Observable listeners and update component's list automatically each time the service source has been changed
tasks: Array<Tasks>;
constructor(private tasksProvider: TasksProvider) {
this.tasksProvider.$tasks.subscribe(tasks => this.tasks = tasks);
}
ngOnInit() {
this.tasksProvider.getAllTasks();
}
public updateCheckboxValue(id: number, name: string, event: any){
this.tasksProvider.updateTask(id, name, event.checked);
}
Related
Hi Every one here
I face problem with fetching data to array but when I put data to array the editor said not defined array
Error Message:
Failed to compile.
src/app/customers/customers-list/customers-list.component.ts:111:14 - error TS2551: Property 'CUSTOMERS' does not exist on type 'CustomersListComponent'. Did you mean 'customers$'?
111 this.CUSTOMERS = posts;
~~~~~~~~~
src/app/customers/customers-list/customers-list.component.ts:64:3
64 customers$: Observable<Customer[]>;
~~~~~~~~~~
'customers$' is declared here.
This is the CODE
import {
Component,
OnInit,
PipeTransform, // table
} from '#angular/core';
import { DecimalPipe } from '#angular/common'; // table
import { FormControl } from '#angular/forms'; // table
import { Observable } from 'rxjs'; // table
import { map, startWith } from 'rxjs/operators'; // table
import {NgbModal} from '#ng-bootstrap/ng-bootstrap'; // modal
import {AddCustomerComponent} from '../add-customer/add-customer.component'; // modal
import { faFolderPlus, faPencilAlt, faTrashAlt } from '#fortawesome/free-solid-svg-icons'; // fontawsome icons
import {HttpClient} from '#angular/common/http';
// table
interface Customer {
id: number;
name: string;
company: string;
remaining: number;
email: string;
mobile: number;
whats_up: number;
}
let CUSTOMERS: Customer[] = [
{
id: 12,
name: 'jack',
company: 'SDTE',
remaining: 580,
email: 'test#test.com',
mobile: +456456456456,
whats_up: +456456456
}
];
function search(text: string, pipe: PipeTransform): Customer[] {
return CUSTOMERS.filter(customer => {
const term = text.toLowerCase();
return customer.name.toLowerCase().includes(term)
|| customer.company.toLowerCase().includes(term)
|| pipe.transform(customer.remaining).includes(term)
|| customer.email.toLowerCase().includes(term)
|| pipe.transform(customer.mobile).includes(term)
|| pipe.transform(customer.whats_up).includes(term);
});
}
#Component({
selector: 'app-customers-list',
templateUrl: './customers-list.component.html',
styleUrls: ['./customers-list.component.css'],
providers: [DecimalPipe] // table
})
export class CustomersListComponent implements OnInit {
// table
customers$: Observable<Customer[]>;
filter = new FormControl('');
faFolderPlus = faFolderPlus;
faPencilAlt = faPencilAlt;
faTrashAlt = faTrashAlt;
constructor(
pipe: DecimalPipe, // table
private modalService: NgbModal, // modal
private http: HttpClient // Get All Data
) {
// table
this.customers$ = this.filter.valueChanges.pipe(
startWith(''),
map(text => search(text, pipe))
);
}
ngOnInit(): void {
this.getAllData();
}
// modal
openPopupModal() {
const modalRef = this.modalService.open(AddCustomerComponent,{ centered: true, size: 'lg' });
modalRef.componentInstance.name = 'World';
}
private getAllData() {
this.http
.get('http://localhost:3000/customers')
.subscribe(
posts => {
console.log('GET all Data works');
this.CUSTOMERS = posts; // <<<<< Here is the problem ************ How can I to Fix it.
});
}
}
I
this.CUSTOMERS = posts; this refers to current class CustomersListComponent but your variable is outside the class so you need to assign directly CUSTOMERS = posts; :)
You need to specify the return type.
You could try using this:
private getAllData() {
this.http
.get<Customer[]>('http://localhost:3000/customers') // <<<<< Try using this.
.subscribe(
posts => {
console.log('GET all Data works');
CUSTOMERS = posts;
});
}
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;
});
}
Dear I am developing a page with Angular 7 and I am presented with the error TS2559: Type 'BookInterface[]' has no properties in common with type 'BookInterface', I have changed the code but I still can not find the solution, I leave the code below, the error is thrown in the method getListBooks(): this is my file list-books.component.ts
import { BookInterface } from './../../../models/book';
import { DataApiService } from './../../../services/data-api.service';
import { Component, OnInit } from '#angular/core';
import {NgForm} from '#angular/forms';
#Component({
selector: 'app-list-book',
templateUrl: './list-book.component.html',
styleUrls: ['./list-book.component.css']
})
export class ListBookComponent implements OnInit {
constructor(private dataApi: DataApiService) { }
private books: BookInterface = {};
ngOnInit() {
this.getListBooks();
}
getListBooks() {
this.dataApi.getAllBooks().subscribe(books => {
this.books = books;
});
}
onDelete() {
console.log('LIBRO ELIMINADO');
}
}
I also leave the code of my data-api.service.ts from where I call the interface
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs/internal/Observable';
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '#angular/fire/firestore';
import { BookInterface } from '../models/book';
#Injectable({
providedIn: 'root'
})
export class DataApiService {
constructor(private afs: AngularFirestore) {
this.bookCollecction = afs.collection<BookInterface>('books');
this.books = this.bookCollecction.valueChanges();
}
private bookCollecction: AngularFirestoreCollection<BookInterface>;
private books: Observable<BookInterface[]>;
private bookDoc: AngularFirestoreDocument<BookInterface>;
private book: Observable<BookInterface>;
getAllBooks() {
return this.books = this.bookCollecction.snapshotChanges()
.pipe(map( changes => {
return changes.map( action => {
const data = action.payload.doc.data() as BookInterface;
data.id = action.payload.doc.id;
return data;
});
}));
}
// metodo que trae un libro a traves de su id
getOneBook(idBook: string) {
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
return this.book = this.bookDoc.snapshotChanges().pipe(map(action => {
if (action.payload.exists === false){
return null;
} else {
const data = action.payload.data() as BookInterface;
data.id = action.payload.id;
return data;
}
}));
}
addBook(book: BookInterface): void {
this.bookCollecction.add(book);
}
updateBook(book: BookInterface): void {
let idBook = book.id;
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
this.bookDoc.update(book);
}
deleteBook(idBook: string): void {
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
this.bookDoc.delete();
}
}
The version of typescript that I am currently using is Version 2.7.2, but also update it without solving the problem
You need to change the following:
private books: BookInterface = {};
to:
private books: BookInterface[] = [];
Passing the URL id from the last page a user was on to a service that I can reference in a dialog.
issuer.service.ts
import { Injectable, EventEmitter } from '#angular/core';
import { Observable, of } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class IssuerService {
private urlidSource = new BehaviorSubject<string>('');
currentUrlid = this.urlidSource.asObservable();
public onChange: EventEmitter<string> = new EventEmitter<string>();
constructor () {
}
changeUrlid(urlid: string) {
this.currentUrlid = of(urlid);
this.onChange.emit(urlid);
}
getUrlid(currentUrlid: string) {
return this.currentUrlid;
}
}
Page that has the URL id I want (dashboard.component.ts)
import { IssuerService } from './../../issuer.service';
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
newUrlid() {
this.issuerService.changeUrlid(this.route.snapshot.paramMap.get('id'));
console.log(this.urlid);
}
ngOnInit() {
// Get URL ID
this.issuerService.onChange.subscribe(urlid => this.urlid = urlid);
this.newUrlid();
}
Component I want to read the value in:
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
import { IssuerService } from './../../issuer.service';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
ngOnInit() {
this.issuerService.onChange.subscribe(urlid => {
this.urlid = urlid;
console.log(this.urlid);
});
}
So currently when I visit my dashboard page it will display the value of 2 which is correct. My goal is that when a user visits any page I can read this value of 2. How can I access this value? The above code works and my Header displays 2 but only when on the dashboard page. I need it to display 2 no matter what page the user is on.
you can see this example, and It's modified list:
use queryPamas to get query string, not params (DashboardComponent)
use ReplaySubject(1) to return the last urlId; it's don't have a default value, just return prev one value (IssuerService)
get observable from getUrlid and subscribe it in components that want to show url id
export class IssuerService {
private urlidSource = new ReplaySubject<string>(1);
constructor() {
}
changeUrlid(urlid: string) {
this.urlidSource.next(urlid);
}
getUrlid() {
return this.urlidSource;
}
}
export class DashboardComponent implements OnInit {
urlid: string;
constructor(
// private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
// public dialog: MatDialog
) { }
newUrlid() {
// Get URL ID
this.route.queryParams.subscribe((queryParam) => {
const id = queryParam['id'];
if (!id) {
return;
}
this.issuerService.changeUrlid(id);
});
}
ngOnInit() {
this.newUrlid();
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
export class HelloComponent implements OnInit {
urlid;
constructor(
private issuerService: IssuerService
) { }
ngOnInit() {
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
You do not need a parameter for your get Method since you already have the value inside the service,
getUrlid() {
return this.currentUrlid;
}
and you can use retrieve the value in the 2nd component as follows,
this.issuerService.currentUrlid.subscribe((value: string) => {
this.urlid = value;
}
I am trying to get my JSON response from the HttpClient service into an array so that I can loop through using *ngFor in my html. I've tried using "this" to loop through but *ngFor will not accept it. Below is the code for my service.ts component and the main component.ts.
I just need some way to convert an array from "resp.body" into an exportable Array to be used for string interpolation in the html. Any help would be much appreciated!
races.component.ts
import { Component, OnInit } from '#angular/core';
import {Race, RacesService} from './races.service';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'dh-races',
templateUrl: './races.component.html',
providers: [ RacesService ],
styleUrls: ['./races.component.scss']
})
export class RacesComponent {
error: any;
headers: string[];
race: Race;
raceM: any[];
constructor(private racesService: RacesService) {
var raceM = [];
var raceArray = [];
this.racesService.getRaceResponse()
.subscribe(resp => {
raceArray.push(resp.body);
for (let obj of raceArray) {
for (let i in obj) {
raceM.push({
"ID": obj[i].id + ",",
"Date": obj[i].activityStartDate,
"RaceName": obj[i].assetName,
"Website": obj[i].website
})
}
console.log(raceM);
return raceM;
}
});
}
races.service.ts
#Injectable()
export class RacesService {
constructor(private httpClient: HttpClient) { }
getRace() {
return this.httpClient.get(activeApiURL).pipe(
retry(3),
catchError(this.handleError)
);
}
getRaceResponse(): Observable<HttpResponse<Race>> {
return this.httpClient.get<Race>(
activeApiURL, {
observe: 'response'
});
}
To fix the issue, you need to create an interface that matches the data you get from the server, I will call this interface IRace.
Then in the component I will create a variable named races, I will assign the returned value from the server response i.e. resp.body to the races variable.
I'd change the service to look like this:
export interface IRace {
// Your response from server object's properties here like so:
id: Number;
assetName: string;
...
}
export class RacesService {
constructor(private httpClient: HttpClient) { }
getRace() {
return this.httpClient.get(activeApiURL).pipe(
retry(3),
catchError(this.handleError)
);
}
getRaceResponse(): Observable<HttpResponse<Array<Race>>> {
return this.httpClient.get<Array<Race>>(
activeApiURL, {
observe: 'response'
});
}
}
Finally, I'd change the race component to this:
import { Component, OnInit } from '#angular/core';
import { Race, RacesService, IRace } from './races.service';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'dh-races',
templateUrl: './races.component.html',
providers: [ RacesService ],
styleUrls: ['./races.component.scss']
})
export class RacesComponent {
error: any;
headers: string[];
races: IRace[];
constructor(private racesService: RacesService) {
this.racesService.getRaceResponse()
.subscribe(resp => {
this.races = resp.body;
});
}
}
I hope this helps.