I got upload.service and 2 different modules with some components.
upload.service , upload.ts is imported to components->
Upload.module (
upload.component (everything works fine (i can upload and get photo
url data from database)
)
Ui.module )
upload.component (same function but I can't see photo , no url)
)
Working component (upload.component) :
import { Component, OnInit } from '#angular/core';
import { UploadService } from '../shared/upload.service';
import { Upload } from '../shared/upload';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'uploads-list',
templateUrl: './uploads-list.component.html',
styleUrls: ['./uploads-list.component.scss'],
})
export class UploadsListComponent implements OnInit {
uploads: Observable<Upload[]>;
showSpinner = true;
constructor(private upSvc: UploadService) { }
ngOnInit() {
this.uploads = this.upSvc.getUploads();
this.uploads.subscribe(() => this.showSpinner = false);
}
}
Not working component ( ui.component 0 errors) :
import {Component, OnInit} from '#angular/core';
import { UploadService } from "../../uploads/shared/upload.service";
import { Upload } from "../../uploads/shared/upload";
import { Observable } from "rxjs/Observable";
#Component({
selector: 'top-nav',
templateUrl: './top-nav.component.html',
styleUrls: ['./top-nav.component.scss'],
})
export class TopNavComponent implements OnInit {
uploads: Observable<Upload[]>;
show = false;
showSpinner = true;
toggleCollapse() {
this.show = !this.show;
}
constructor(private upSvc: UploadService) { }
ngOnInit() {
this.uploads = this.upSvc.getUploads();
console.log("paimama upload :", this.uploads)
this.uploads.subscribe(() => this.showSpinner = false);
}
}
I think the main problem is that I am trying to use these functions and variables from one service in two different modules - components. How to retrieve it so it would work ?
Edit (the function I am calling) :
getUploads() {
this.uploads = this.db.list(`${this.auth.userId}`).snapshotChanges().map((actions) => {
return actions.map((a) => {
const data = a.payload.val();
const $key = a.payload.key;
return { $key, ...data };
});
});
return this.uploads;
}
Html (working one in upload.component) :
<h3>File Uploads</h3>
<div *ngFor="let upload of uploads | async">
<upload-detail [upload]='upload'></upload-detail>
<img src="{{upload.url}}" alt="">
</div>
<loading-spinner *ngIf="showSpinner"></loading-spinner>
<hr>
<upload-form></upload-form>
HTML (not working ui.component)
<div *ngFor="let upload of uploads | async">
<img src="{{upload.url}}" alt="">
</div>
or
It seems that you are re-initializing your service observable each time the getUploads() method is called. This causes each component to be subscribing to a different observable, so they will not be in sync.
Don't do this if you want all components to subscribe to the same stream.
Change this:
getUploads() {
this.uploads = this.db.list(`${this.auth.userId}`).snapshotChanges().map((actions) => {
return actions.map((a) => {
const data = a.payload.val();
const $key = a.payload.key;
return { $key, ...data };
});
});
return this.uploads;
}
To this:
getUploads() {
if (!this.uploads) {
this.uploads = this.db.list(`${this.auth.userId}`).snapshotChanges().map((actions) => {
return actions.map((a) => {
const data = a.payload.val();
const $key = a.payload.key;
return { $key, ...data };
});
});
}
return this.uploads;
}
Now each component should get the same exact observable stream.
Let me know if that doesn't help.
Update:
Another approach you can take is to create a custom subject and use that as an observable. Subscribe to that observable in both components, and when you get uploads from the db you can add the upload to the stream. Each component will get the upload you add to that stream.
#Injectable()
export class UploadService {
uploadSubject: ReplaySubject<Upload> = new ReplaySubject();
upload$: Observable<Upload> = this.uploadSubject.asObservable();
getUploads() {
this.http.get(url).subscribe((upload: Upload) => {
this.uploadSubject.next(upload);
});
}
}
Now in any component, you can just subscribe to upload$ and any time the getUploads() is called, every single component subscribing to upload$ will get the value.
export class Component1 {
upload: Upload;
constructor(private uploadService: UploadService) {
uploadService.upload$.subscribe((upload: Upload) => this.upload = upload);
}
}
export class Component2 {
upload: Upload;
constructor(private uploadService: UploadService) {
uploadService.upload$.subscribe((upload: Upload) => this.upload = upload);
}
}
With this approach, just make sure to call getUploads somewhere, because the components are not calling it; they are just listening for the value it will broadcast.
Update 2:
Now I am modifying your code to work with my example above. Since this is using your code, it may not work because I may be missing certain things you have in your environment. But the original "Update" above works 100%, so use that as a guideline to get your code working. Just read the code and make sure you understand what it does and you will see that it will work for what you're doing.
UploadService:
#Injectable()
export class UploadService {
uploadStream: ReplaySubject<Upload[]> = new ReplaySubject();
uploads$: Observable<Upload[]> = this.uploadStream.asObservable();
constructor() {
this.getUploads();
}
getUploads() {
this.db.list(`${this.auth.userId}`)
.map((actions: Action[]) => {
return actions.map((action: Action) => {
const data = action.payload.val();
const $key = action.payload.key;
return new Upload({ $key, ...data });
});
})
.subscribe((uploads: Upload[]) => {
this.uploadStream.next(uploads);
});
}
}
UploadsListComponent:
import { Component } from '#angular/core';
import { UploadService } from '../shared/upload.service';
#Component({
selector: 'uploads-list',
templateUrl: './uploads-list.component.html',
styleUrls: ['./uploads-list.component.scss'],
})
export class UploadsListComponent {
constructor(public uploadService: UploadService) {}
}
UploadListComponent template:
<h3>File Uploads</h3>
<div *ngFor="let upload of uploadService.uploads$ | async">
<upload-detail [upload]='upload'></upload-detail>
<img src="{{upload.url}}" alt="">
</div>
<loading-spinner *ngIf="showSpinner"></loading-spinner>
<hr>
<upload-form></upload-form>
Now you can take the same component approach as above on your second component.
Related
I want to display the data from my backend to the front end. It's working but I am making a shopping cart system in Angular using help from a youtube video. I have poor knowledge of Observables and stuff related to it. For the Youtuber, the data is displayed. The only difference between the youtube project and mine is that he is using a fake store API and I am using a database and getting products from the backend.
My cart.component.ts file
import { Component, OnInit } from '#angular/core';
import { CartService } from 'src/services/cart.service';
#Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit {
products: any = [];
allProducts: any = 0;
constructor(private cartService: CartService) { }
ngOnInit(): void {
this.cartService.getProductData().subscribe(res => {
this.products = res;
this.allProducts = this.cartService.getTotalAmount();
})
}
removeProduct(item: any) {
this.cartService.removeCartData(item);
}
removeAllProducts() {
this.cartService.removeAllCart();
}
}
My cart.service.ts file
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class CartService {
cartDataList: any = [];
productList = new BehaviorSubject<any>([]);
constructor() { }
// Get cart data
getProductData() {
return this.productList.asObservable();
}
// Set cart data
setProduct(product: any) {
this.cartDataList.push(...product);
this.productList.next(product);
}
// Add products to cart
addToCart(product: any) {
this.cartDataList.push(product);
this.productList.next(this.cartDataList);
this.getTotalAmount();
console.log(this.cartDataList);
}
// Calculate total amount
getTotalAmount() {
let grandTotal = 0;
this.cartDataList.map((a: any) => {
grandTotal += a.total;
});
}
// Remove product one by one
removeCartData(product: any) {
this.cartDataList.map((a: any, index: any) => {
if (product.id === a.id) {
this.cartDataList.splice(index, 1);
}
})
}
// Empties the whole cart
removeAllCart() {
this.cartDataList = [];
this.productList.next(this.cartDataList);
}
}
I know the problem is in getProductData() function but I don't know how to fix it. Also if you need any other file that may help feel free to ask and yes this is my very first post asking a question.
productList = new BehaviorSubject([]);
getProductData$ = this.productList.asObservable();
do not use this.productList.asObservable() in a function declare it as a variable.
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;
});
}
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) => {}
);
}
I am new to Angular and Ionic. I am looping through an array of content that is store in my Firestore database. When the app recompiles and loads, then I go to the settings page (that's where the loop is happening), I see the array of content just fine. I can update it on Firestore and it will update in real time in the app. It's all good here. But if I click "Back" (because Settings is being visited using "navPush"), then click on the Settings page again, the whole loop content will be gone.
Stuff is still in the database just fine. I have to recompile the project to make the content appear again. But once again, as soon as I leave that settings page, and come back, the content will be gone.
Here's my code:
HTML Settings page (main code for the loop):
<ion-list>
<ion-item *ngFor="let setting of settings">
<ion-icon item-start color="light-grey" name="archive"></ion-icon>
<ion-label>{{ setting.name }}</ion-label>
<ion-toggle (ionChange)="onToggle($event, setting)" [checked]="setting.state"></ion-toggle>
</ion-item>
</ion-list>
That Settings page TS file:
import { Settings } from './../../../models/settings';
import { DashboardSettingsService } from './../../../services/settings';
import { Component, OnInit } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-dashboard-settings',
templateUrl: 'dashboard-settings.html',
})
export class DashboardSettingsPage implements OnInit {
settings: Settings[];
checkStateToggle: boolean;
checkedSetting: Settings;
constructor(public dashboardSettingsService: DashboardSettingsService) {
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
}
onToggle(event, setting: Settings) {
this.dashboardSettingsService.setBackground(setting);
}
}
And my Settings Service file (the DashboardSettingsService import):
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return this.settings;
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
Any idea what is causing that?
My loop was in a custom component before, so I tried putting it directly in the Dashboard Settings Page, but it's still not working. I have no idea what to check here. I tried putting the :
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
})
...part in an ngOninit method instead, or even ionViewWillLoad, and others, but it's not working either.
I am using Ionic latest version (3+) and same for Angular (5)
Thank you!
From the Code you posted i have observed two findings that might be the potential cause for the issue ,
Calling of the Service method in the constructor :
When your setting component is created , then that constructor will be called but but if you were relying on properties or data from child components actions to take place like navigating to the Setting page so move your constructor to any of the life cycle hooks.
ngAfterContentInit() {
// Component content has been initialized
}
ngAfterContentChecked() {
// Component content has been Checked
}
ngAfterViewInit() {
// Component views are initialized
}
ngAfterViewChecked() {
// Component views have been checked
}
Even though you add your service calling method in the life cycle events but it will be called only once as you were subscribing your service method in the constructor of the Settings service file . so just try to change your service file as follows :
getSettings() {
this.settingsCollection = this.afs.collection('settings');
this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
Update :
Try to change the Getsettings as follows and please do update your question with the latest changes
getSettings() {
this.settingsCollection = this.afs.collection('settings');
return this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
I'm not certain, but I suspect the subscription to the settings observable settings: Observable<Settings[]> could be to blame. This may work on the first load because the DashboardSettingsService is being created and injected, therefore loading the settings, and then emitting an item (causing your subscription event in DashboardSettingsPage to fire).
On the second page load, DashboardSettingsService already exists (services are created as singletons by default) - this means that the constructor does not get called (which is where you set up your observable) and therefore it does not emit a new settings object for your component.
Because the Observable does not emit anything, the following event will not be fired, meaning your local settings object is never populated:
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
You could refactor your service with a method that provides the latest (cached) settings object, or a new Observable (dont forget to unsubscribe!!), rather than creating a single Observable which will only be triggered by creation or changes to the underlying storage object.
Here's a simple example that doesnt change your method signature.
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
cachedSettings: Settings[];
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
this.cachedSettings = data;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return Observable.of(this.cachedSettings);
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
So I have two not related components and I'm trying to communicate between them using a service and a BehaviorSubject. Everything is cool, data is exchanged, but when i call the service from one of the components, it doesn't trigger change detection on the other component.
So to show what I'm talking about in code:
The service:
import {Injectable, Optional, EventEmitter} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { forEach } from '#angular/router/src/utils/collection';
#Injectable()
export class SbkService {
items: any = [];
private _itemsSource = new BehaviorSubject<any>(0);
items$ = this._itemsSource.asObservable();
constructor (
private _localStorageService: LocalStorageService
) {}
storeSelection(item) {
this.items.push(item);
this.setLocalStorage();
}
removeSelection(selectionId) {
for (var i = this.items.length-1; i >= 0; i--) {
if (this.items[i].selectionId == selectionId)
this.items.splice(i, 1);
}
this.setLocalStorage();
return true;
}
getLocalStorage() {
this.items = this._localStorageService.get('items');
this._itemsSource.next(this.items);
return this.items;
}
setLocalStorage() {
this._localStorageService.set('items', this.items);
this._itemsSource.next(this.items);
return true;
}
}
Component 1:
import { Component, OnInit } from '#angular/core';
import { SbkService } from '../../services/sbk.service'
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-right-sidebar',
template: `<ul>
<li *ngFor="let selection of selections">
{{selection.name}}
<span class="cutom-btn" (click)="remove(selection.selectionId)">
delete
</span>
</li>
</ul>`,
styles: []
})
export class RightSidebarComponent implements OnInit {
selections: any = [];
subscription:Subscription;
constructor (
private _sbkService: SbkService
) {
}
ngOnInit() {
this.subscription = this._sbkService.items$
.subscribe(selections => {
this.selections = selections })
this._sbkService.getLocalStorage();
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
remove(selectionId) {
this._sbkService.removeSelection(selectionId);
}
}
Component 2:
import { Component, ViewChild, ElementRef } from '#angular/core';
import 'rxjs/add/operator/map';
import {forEach} from '#angular/router/src/utils/collection';
import {SbkService} from '../services/sbk.service'
#Component({
selector: 'app-match-table',
template: `
<div (click)="addItem('mumble', 1)">Add mumble</div>
<div (click)="addItem('ts', 2)">Add ts</div>
<div (click)="addItem('discord', 3)">Add discord</div>
`,
styles: []
})
export class MatchTableComponent {
constructor(
private _sbkService: SbkService
) {}
//Place a bet in the betslip
public addItem = (name, selectionId) => {
item: Object = {};
item.selectionId = selectionId;
item.name = name;
this._sbkService.storeSelection(item);
}
}
So, when I click on a div from component 2 (MatchTableComponent) it updates the selections array in component 1 (RightSideBarComponent) but doesn't trigger a change detection, so the sorted list doesn't get updated until i refresh the page. When i click on delete from RightSideBarComponent template, it updates the selections array and triggers the change detection.
How can I make this work? I tried subscribing to an event from SbkService in the AppComponent and from there triggering the setLocalStorage from SbkService, but no luck...
If I'm not wrong, you should set the next "sequence" on your Observable "items" through your BehaviourSubject.
Could you modify and try this?:
storeSelection(item){
const itemsAux = this._itemsSource.getValues();
itemsAux.push(item);
this._itemsSource.next(itemsAux);
}
setLocalStorage(){
this._localStorageService('items', this._itemsSource.getValues();
return true;
}