Angular Material table Datasource wont display array - javascript

Im having an issue displaying data in a table using datasource i will add my code below please let me know if u see what im doing wrong here I cant see to figure it out
export interface userData {
email: string;
plans: [];
}
export interface PlanData {
amount: number;
channel: string;
expiration: Date;
active: boolean;
};
#Component({
selector: 'app-plan-manager',
templateUrl: './plan-manager.component.html',
styleUrls: ['./plan-manager.component.css']
})
export class PlanManagerComponent implements OnInit {
displayedColumns: string[] = ['active', 'amount', 'channel', 'power'];
array = []
dataSource: PlanData[] = [];
constructor(public router: Router, private firestore: AngularFirestore, private auth: AngularFireAuth, private snackBar: MatSnackBar) { }
ngOnInit(): void {
this.auth.user.subscribe(user => {
this.firestore.collection('users').doc<userData>(user.email).valueChanges().subscribe(userDoc => {
if(userDoc.plans.length === 0){
this.snackBar.open('You have no active plans please purchase a plan here and it will be listed on the dashboard','',{duration: 5000})
this.router.navigate(['plans'])
return;
}
userDoc.plans.forEach((plan, i) => {
this.firestore.collection('plans').doc<PlanData>(plan).valueChanges().subscribe(data => {
//data looks like this {amount: 45, active: true, channel: "test"}
this.dataSource.push(data)
})
})
})
})
}
please let me know if any more code snippets is required ive reread the docs many times on this and just cant figure it out any help would be appreciated
Im able to get it to display 1 row of data using this code here but for some reason it will only display the first object in the datasource nothing else i have a feeling that has something todo with the forEach loop
export interface userData {
email: string;
plans: [];
}
export interface PlanData {
amount: number;
channel: string;
active: boolean;
};
#Component({
selector: 'app-plan-manager',
templateUrl: './plan-manager.component.html',
styleUrls: ['./plan-manager.component.css']
})
export class PlanManagerComponent implements OnInit {
displayedColumns: string[] = ['active', 'amount', 'channel'];
array = []
dataSource: PlanData[] = [];
constructor(private changeDetectorRefs: ChangeDetectorRef, public router: Router, private firestore: AngularFirestore, private auth: AngularFireAuth, private snackBar: MatSnackBar) { }
ngOnInit(): void {
this.auth.user.subscribe(user => {
this.firestore.collection('users').doc<userData>(user.email).valueChanges().subscribe(userDoc => {
if(userDoc.plans.length === 0){
this.snackBar.open('You have no active plans please purchase a plan here and it will be listed on the dashboard','',{duration: 5000})
this.router.navigate(['plans'])
return;
}
userDoc.plans.forEach((plan, i) => {
this.firestore.collection('plans').doc<PlanData>(plan).valueChanges().subscribe(data => {
this.array.push(data)
this.dataSource = this.array
})
})
})
})
}```

Well after rereading docs i have found i was being totally ineffecient and rewrote the code this does everything i was trying to achieve below.
export class PlanManagerComponent implements OnInit {
displayedColumns: string[] = ['active', 'amount', 'channel'];
dataSource: MatTableDataSource<any>;
constructor(private changeDetectorRefs: ChangeDetectorRef, public router: Router, private firestore: AngularFirestore, private auth: AngularFireAuth, private snackBar: MatSnackBar) { }
ngOnInit(): void {
this.dataSource = new MatTableDataSource();
this.auth.user.subscribe(user => {
console.log(user.email)
this.firestore.collection('users').doc(user.email).collection('/plans').valueChanges().subscribe(userDoc => {
this.dataSource.data = userDoc;
});
});
};
};

Related

Angular how to call Method from Sibling Component that is binded via <router-outlet>?

I have a Project that uses HTTP Calls to fetch Data from API Endpoint and than display on Screen.
It's a simple ToDoList. So you can add Items to the list, see all Items in your List, delete items and so on.
The Project structure is this:
Items-Component (Holds the entire App basically)
Item-list-component
Item-detail-component
Item-edit-component
item-add-component
Item.service
The Items.component.html looks like this:
<div class="row">
<div class="col-md-5">
<app-item-list></app-item-list>
</div>
<div class="col-md-7">
<router-outlet></router-outlet>
</div>
So we can see that the item-list-component and the other 3 components (binded via router-outlet) are sibling components, that's what I think.
So my Problem is now:
I want that whenever a new Item is created the items[] in the items.list component should refresh automatically. Now I must click a "Fetch Items" button to refresh the items[].
When I add a new Item, it fires a method from my item.service, it holds a fetchItems Method that just returns an Observable of the API Endpoint, like this:
Item-add component.ts:
#Component({
selector: 'app-item-add',
templateUrl: './item-add.component.html',
styleUrls: ['./item-add.component.css']
})
export class ItemAddComponent implements OnInit {
constructor(private itemService: ItemService, private route: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
}
onCreatePost(item: Item) {
// Send Http request
this.itemService.createAndStorePost(item.description, item.isComplete);
//Here I want that the items[] in the items.list component refreshes when I add new Item
this.onCancel();
}
onCancel() {
this.router.navigate([''], {relativeTo: this.route});
}
}
And the item.service.ts:
#Injectable()
export class ItemService {
constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router) {
}
fetchItems(): Observable<Item[]> {
return this.http.get<Item[]>('https://localhost:44321/api/TodoItems');
}
fetchItem(id: number): Observable<Item> {
return this.http.get<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
createAndStorePost(description: string, isComplete: boolean) {
var item = { description: description, isComplete: isComplete };
this.http.post('https://localhost:44321/api/TodoItems', item)
.subscribe(Response => {
});
}
deleteItem(id: number): Observable<Item> {
return this.http.delete<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
updateItem(id:number, item: Item) {
this.http.put<Item>('https://localhost:44321/api/TodoItems' + '/' + id, item).subscribe();
}
}
Then the items-list component catches that Observable and subscribes to it and sets the Response from that subscription to and items[] in the component itself:
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
constructor(private route: ActivatedRoute, private router: Router, private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
});
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
}
What can I do to trigger that the items.list should fetch Items again?
I can't use #ViewChild because it is no Parent-Child relation.
Can I implement and instance of item.list anywhere in the project and just call the onFetchItems Method?
Thanks!
you can use BehaviorSubject to share data between your different components.
Here is an example:
In your ItemService.
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ItemService {
private _itemsSource = new BehaviorSubject([]);
currentItems = this._itemsSource.asObservable();
constructor() { }
updateItems(items: []): void {
this._itemsSource.next(items)
}
}
In your ItemsComponent, you update the new value in the service after you get all the items,
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
items: Item[] = [];
constructor(private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
this.updateItems(this.items)
});
}
updateItems(newItems: []): void {
this.itemService.updateItems(newItems)
}
}
And in your ItemListComponent
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
subscription: Subscription;
constructor(private route: ActivatedRoute,
private router: Router,
private itemService: ItemService) { }
ngOnInit(): void {
this.subscription = this.itemService.currentItems.subscribe(items => this.items = items)
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}

How to fix filter by column in angular?

I'm write project for learning Angular. My project displays posts. I want to filter posts by id and title. I need filtering logic in post.service.ts. I made filtering by ID (it works fine), but filtering by title works strange. How to fix filtering by title?
All project
posts.service.ts:
import { HttpClient } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
export interface Post {
title: string,
body?: string;
userId?: number,
id?: number,
show?: any,
user?: string,
}
#Injectable({providedIn: 'root'})
export class PostService {
private _postsURL = "https://jsonplaceholder.typicode.com";
constructor(private http: HttpClient) {}
public fetchPosts(page: number, itemsPerPage: number, id: any, title: any): Observable<Post[]> {
let posts = this.http.get<Post[]>(`${this._postsURL}/posts`);
if (id) {
posts = this.http.get<Post[]>(`${this._postsURL}/posts?userId=${id}`)
}
if (title) {
posts = posts
.pipe(
map((posts: any) => {
return posts.map((post: any) => {
console.log(post.title.toUpperCase().includes(title.toUpperCase()))
return {
title: post.title.toUpperCase().includes(title.toUpperCase()) ? post.title : '',
}
})
})
);
}
return this.getPageItems(posts, page, itemsPerPage);
}
private getPageItems(posts: Observable<Post[]>, page: number, itemsPerPage: number): Observable<Post[]> {
return posts.pipe(
map(u => {
let startIndex = itemsPerPage * (page - 1);
return u.slice(startIndex, startIndex + itemsPerPage);
})
);
}
getById(id: number): Observable<Post> {
return this.http.get<Post>(`${this._postsURL}/posts/${id}`);
}
}
posts.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms';
import { Post, PostService } from '../post.service';
import { User, UserService } from '../user.service';
#Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.scss']
})
export class PostsComponent implements OnInit {
page: any = 1
posts: Post[] = []
users: User[] = []
columns: any = [
{ title: 'user id', key: 'userId' },
{ title: 'Имя пользователя', key: 'userName' },
{ title: 'Заголовок', key: 'titleArticle' }
];
form!: FormGroup
public totalItems: number = 100;
public itemsPerPage: number = 12;
error = ''
constructor(
private postService: PostService,
private userService: UserService,
) {}
ngOnInit() {
this.fetchPosts();
this.fetchUsers();
this.form = new FormGroup({})
for (let column of this.columns) {
this.form.addControl(column.key, new FormControl(''));
}
this.form.valueChanges.subscribe(values => {
// call fetch for filtering
this.fetchPosts(values['userId'], values['titleArticle'])
});
}
fullData() {
return this.posts.map(post => ({
post,
user: this.users.find(user => user.id === post.userId) ?? { username: '' },
}));
}
onChangePage(event: any) {
this.page = event;
this.fetchPosts();
}
fetchPosts(id?: any, title?: any) {
this.postService.fetchPosts(this.page, this.itemsPerPage, id, title)
.subscribe(posts => {
this.posts = posts
}, error => {
this.error = error.message
})
}
fetchUsers() {
this.userService.fetchUsers()
.subscribe(users => {
this.users = users
})
}
}
You can update the if condition as below:
if (title) {
posts = posts.pipe(
map((posts: any) => {
return posts.filter(item => item.title.includes(title));
})
);
}

convert returned Observables to custom class array in angular

Hello folks I will keep my question very simpler by showing code
I am using Json placeholder site for the fake rest Api
I have a user class Object
I want to convert returned Observable to the
custom class object array.
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { Users } from './users.model';
#Injectable({
providedIn: 'root'
})
export class UsersService {
private url = "https://jsonplaceholder.typicode.com";
constructor(private http:HttpClient) {
console.log(this.getUsers());
}
getUsers():Observable<Users[]>{
return this.http.get<Users[]>(`${this.url}/posts`);
}
}
The above is my service
export class Users {
email: string;
id: number;
name: string;
phone: string;
username: string;
}
above is my class I haven't included all properties
In my typescript file I have code like.
constructor(private _usersService:UsersService) {
}
ngOnInit(): void {
this._usersService.getUsers().subscribe(data=>this.users=data);
console.log(this.users);
}
Now the things I want is
how to convert returned observable in my custom class object?
I don't have all the fields so how is it possible to map only those fields which I want?
Hope my question is clear..!!
so this answer takes advantage of map() which is imported from rxjs.
before subscribing we are going to pipe a map() function into the observable stream and then map() each element from that array into a new object that fits our User interface
then we subscribe and the data we get then will be an array that fits our User interface
ngOnInit(): void {
this._usersService.getUsers()
.pipe(map(data => {
return data.map(item => {
const user: User = {
name: item.name,
email: item.email,
}
return user
})
}))
.subscribe(data=>this.users=data);
console.log(this.users);
}
You can do like below, in the User class have a constructor and return User while mapping
import { Component, VERSION, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
export class User {
email: string;
id: number;
name: string;
phone: string;
username: string;
constructor( user: User ) {
Object.assign( this, user );
}
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Angular ' + VERSION.major;
constructor(private http: HttpClient){}
ngOnInit() {
this.http.get<User[]>("https://jsonplaceholder.typicode.com/users")
.pipe(
map( data => {
return data.map( ( user ) => {
return new User( {
email: user['email'],
id: user['id'],
name: user['name'],
phone: user['phone'],
username: user['username'],
} );
} );
} ),
)
.subscribe( (users : User[]) => console.log(users) );
}
}
Working stackblitz

pipe operator not behaving as expected RXJS

Please look at my component below the purpose to is to listen on changes to an input, which it does and then emit the value to the parent component. I created a pipe to only emit every so often and therby minimize the calls to the api, for some reason even though I can see through various console.log statements that it goes in the pipe, it emits the value on every change. What is it that I am missing:
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output, KeyValueDiffers, DoCheck, KeyValueDiffer} from '#angular/core';
import {BehaviorSubject, Observable, of} from "rxjs";
import {debounceTime, distinctUntilChanged, map, skip, switchMap, takeUntil, tap} from "rxjs/operators";
#Component({
selector: 'core-ui-typeahead-filter',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './typeahead-filter.component.html',
})
export class TypeaheadFilterComponent implements DoCheck {
#Input() id: string;
#Input() name: string;
#Input() caption: string;
#Input() placeholder: string;
#Input() cssClass: string;
#Input() cssStyle: string;
#Input() function: any;
#Input() data: Observable<string[]>;
differ: any;
detectChange: string = '';
// term$ = new BehaviorSubject<string>('');
text$ = new Observable<string>();
#Output() onTypeahead: EventEmitter<any> = new EventEmitter<any>();
#Output() onSelect: EventEmitter<any> = new EventEmitter<any>();
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
handleTypeahead = (text$: Observable<string>) =>
text$.pipe(
distinctUntilChanged(),
debounceTime(500),
).subscribe((value) => {
this.onTypeahead.emit(of(value))
})
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$ = of(item.currentValue);
this.handleTypeahead(this.text$);
}
});
}
}
}
More background: There is an ngModel on the input linked to detectChange when it changes then the ngDoCheck is called and executes. Everything is done in observables so in the parent I can subscribe to the incoming events.
EDIT -------------------------------------------------------------------
Tried the following solution based on my understanding of #ggradnig answer, sadly it skips over my pipe something seems wrong with it, really not sure what:
handleTypeahead = (text$: Observable<string>) => {
this.test.subscribe(this.text$);
this.test.pipe(
distinctUntilChanged(),
debounceTime(500),
// switchMap(value => text$)
).subscribe((value) => {
tap(console.log('im inside the subscription',value))
this.onTypeahead.emit(value)
})
}
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$ = of(item.currentValue);
this.handleTypeahead(this.test);
}
});
}
}
}
You can do the following -
export class TypeaheadFilterComponent implements DoCheck {
#Input() id: string;
#Input() name: string;
#Input() caption: string;
#Input() placeholder: string;
#Input() cssClass: string;
#Input() cssStyle: string;
#Input() function: any;
#Input() data: Observable<string[]>;
differ: any;
detectChange: string = '';
// term$ = new BehaviorSubject<string>('');
text$ = new BehaviorSubject<string>('');
serachTerm$: Observable<string>;
#Output() onTypeahead: EventEmitter<any> = new EventEmitter<any>();
#Output() onSelect: EventEmitter<any> = new EventEmitter<any>();
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
// handleTypeahead = (text$: Observable<string>) =>
// text$.pipe(
// distinctUntilChanged(),
// debounceTime(500),
// ).subscribe((value) => {
// this.onTypeahead.emit(of(value))
// })
ngOnInit() {
this.serachTerm$ = this.text$
.pipe(
distinctUntilChanged(),
debounceTime(500),
//filter(), //use filter operator if your logic wants to ignore certain string like empty/null
tap(s => this.onTypeahead.emit(s))
);
}
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$.next(item.currentValue);
}
});
}
}
}
Now, at the bottom of your template put the following line -
<ng-container *ngIf="searchTerm$ | async"></ng-container>
Having this line will keep your component code free form managing the subscription [i.e. need not to subscribe/unsubscribe]; async pipe will take care of it.

share data from service to component after render the function in angular 4

i have service, in service, I have a "cohortTabsResult" method whitch sets the charts array. i want to use this arry in "chart.component"
export class CohortService {
public charts: Array<any>;
cohortTabsResult(obj){
this.charts = []
const subscription = this.cohortDataReq(obj).subscribe(res => {
if(res.status !== 500){
const dataObj = {
definedChart: obj.graph_type,
definedChartData: []
};
this.charts.push(dataObj);
const response = res.json()
//console.log(response)
if (response.error) {
//this.isLoaded = false;
}
else{
Array.prototype.forEach.call(response.data, dataRes => {
const newData = this.getChartDataFormat(dataRes, obj.graph_type, "userType")
dataObj.definedChartData = _.cloneDeep(newData);
});
}
}
});
}
}
and this is my chart.component here I am getting the empty array.
export class ChartCohortComponent implements OnInit{
charts: any;
constructor(private cohortService: CohortService, private route:
Router, public activatedRoute: ActivatedRoute) {
this.charts = this.cohortService.charts;
}
ngOnInit(){
console.log("ch", this.charts)
}
}
import CohortService to your component, add it to the providers in #component, now you can access the variables inside the service. :D
import { CohortService } from '../../cohort.services'; // whatever the path is..
#Component({
selector: '',
templateUrl: '',
styleUrls: [''],
providers: [CohortService]
})
export class ChartCohortComponent implements OnInit{
charts: any;
constructor(private cohortService: CohortService, private route:
Router, public activatedRoute: ActivatedRoute) {
this.charts = this.cohortService.charts;
}
ngOnInit(){
console.log("ch", this.charts)
}
}

Categories

Resources