I'll try to explain this as best I can.
I have a service that contains an observable class that performs tasks to update itself. That observable class needs to be pushed out to the app using the observer located in the service. How can I call that observer from the child without creating some sort of dependency loop?
Here is a rough example:
class MyService {
subClass$: Observable<SubClass>;
_subClassObserver: Observer<SubClass>;
constructor(private _subClassStore: SubClass){
this.subClass$ = new Observable(observer => {
this._subClassObserver = observer
}).share();
}
pushData(){
this._subClassObserver.next(this._subClassStore)
}
}
class SubClass {
displayData: string;
displayData2: number;
constructor(){
socket.on('setData', function(obj){
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
}
}
}
_subClassStore is updating through a stream coming in from socket.io. How do I let MyService know when the SubClass data changes so that it can push it using _subClassObserver.next(_subClassStore)?
EDIT:
I added more details to the example above to show how they are related and utilized.
SubClass is just a listener for a stream of data coming from socket.io and saving the information into the class. It starts listening when MyService is constructed.
The goal of MyService is to provide a bunch these sub classes that can be subscribed to across the app. Each one would allow access to a different data stream and the associated data, but all would be contained within a single service.
The question is how to call the pushData() function in the parent so that it keeps the stream updated for subscribers in the app.
Edit 2:
This might help. below is how it would be written as a service without the sub class. The only reason why I'm not doing this is because there are a substantial amount of these listeners being stored to Observables and abstracting them out into classes makes the information much easier to manage but pushing it to the app is what I can't figure out:
class MyService {
class1$: Observable<DataStream>;
_class1Observer: Observer<DataStream>;
_class1Store: DataStream;
constructor(){
this._class1store = {displayData: 'hello', displayData2: 0};
this.class1$ = new Observable(observer => {
this._class1Observer = observer
}).share();
socket.on('setData', function(obj){
this._class1Store.displayData = obj.dd1;
this._class1Store.displayData2 = obj.dd2;
this._class1Observer.next(this._class1Store)
}
}
interface DataStream = {
displayData: string;
displayData2: number;
}
Instead of function(obj) use ()=> otherwise this won't ponit to the MyService instance.
constructor(){
socket.on('setData', (obj) =>{
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
}
}
I'm not sure but I think socket is prone to run outside Angulars zone.
Try also
constructor(zone:NgZone){
socket.on('setData', (obj) =>{
zone.run(() => {
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
});
}
}
To be able to call a method in MyService from SubClass, SubClass needs a reference to MyService
class MyService {
subClass$: Observable<SubClass>;
_subClassObserver: Observer<SubClass>;
constructor(private _subClassStore: SubClass){
_subClassStore.myService = this;
this.subClass$ = new Observable(observer => {
this._subClassObserver = observer
}).share();
}
pushData(){
this._subClassObserver.next(this._subClassStore)
}
}
class SubClass {
displayData: string;
displayData2: number;
myService:MyService;
constructor(zone:NgZone){
socket.on('setData', (obj) =>{
zone.run(() => {
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
this.myService.pushData();
});
}
}
}
Related
I want to pass an object between 2 components. I created the following shared service:
[PageService Component]
private messageSource = new BehaviorSubject([]);
currentMessage = this.messageSource.asObservable();
changeMessage(message) {
this.messageSource.next(message)
}
And I have implemented it in these 2 components:
[COMPONENT WHEN I GET ON CLICK SONO DATAS]
constructor(private pageService: PageService, private _sanitizer: DomSanitizer) {}
...
onClickMethod(){
self.pageService.getCustomers(self.filters).toPromise().then(response => {
self.searchResults = response;
});
self.pageService.changeMessage(self.searchResults);
}
and
[Component where I need to see above datas]
ngOnInit() {
let self = this;
self.pageService.currentMessage.subscribe(message => self.searchResults = message);
console.log(self.searchResults);
}
Now...if I put the "changeMessage" method in the first component in the method onInit or in the costructor and i try to pass some data like [1,2,3] (so not the response of another api rest) it seems to work...this doesn't work just when i put it inside onClick method and passing "self.searchResults" (the response)...anyone can help me?
Thanks
Go from this
self.pageService.getCustomers(self.filters).toPromise().then(response => {
self.searchResults = response;
});
self.pageService.changeMessage(self.searchResults);
To this
self.pageService.getCustomers(self.filters).toPromise().then(response => {
self.searchResults = response;
self.pageService.changeMessage(self.searchResults);
});
Because you make an HTTP call (I assume), you should wait for the call to end. In your code, it doesn't.
In my web app's client code I have a class responsible for a bunch of websocket IO. This class has a global itemUpdatedObservable that various parts of the UI can subscribe to to do little things. There is also a public function UpdateItem which returns a promise-esq Observable. When the item is updated in response to the call to UpdateItem I want both the returned observable and global observable to emit. The returned observable should also complete after emitting.
I have come up with this solution:
// Singleton
class API {
readonly itemUpdatedObservable: Observable<Item>;
private pendingItemUpdates: { [id: string]: Observer<Item> };
constructor() {
this.itemUpdatedObservable = new Observable(observer => {
socketio.on('itemUpdated', res => {
// do a bunch of validation on item
// ...
if (!res.error) {
observer.next(res.item);
} else {
observer.error(res.error);
}
let pendingObs = pendingItemUpdates[res.id]
if (pendingObs) {
if (!res.error) {
pendingObs.next(res.item);
} else {
pendingObs.error(res.error);
}
pendingObs.complete()
delete pendingItemUpdates[res.id];
}
})
});
this.pendingItemUpdates
}
public UpdateItem(item: Item): Observable<Item> {
const o = new Observable(observer => {
let id = uniqueId(); // Some helper somewhere.
this.pendingItemUpdates[id] = observer;
socketio.emit('updateitem', {item: item, id: id});
}).publish();
o.connect();
return o;
}
}
My question is if there is a cleaner, shorter way of doing this? I have something like 10+ observables in addition to itemUpdatedObservable that all are events for different Object types. This code is messy and unwieldy especially when I am writing it 10x over. Is there a way to streamline the two observables such that I am only calling observable.next(...) or observable.error(...) once?
The above code blob is a simplification of my actual code, there is a lot more validation and context-specific values and parameters in reality.
Maybe you can start with creating some reusable socket function which return observable.
const socketOn = (event) => {
return Observable.create(obs => {
socketio.on(event, res => {
if (!res.error) {
obs.next(res.item);
} else {
obs.error(res.error);
}
})
}).share()
}
// usuage
itemUpdated$=socketOn('itemUpdated')
itemUpdated$.map(res=>...).catch(e=>...)
I am using angular 5
my scenario is from one component im setting the data in service and from another component i'm getting that data|
Component- 1:
makeUser(row){
this.agentsService.setSelectedAgentData(row); // setting the data (row) in agentsService.
const mrf = this.modalService.open(MakeUserComponent);
}
Service:
declaring the varible in class.
public selectedData:any = {};
setter and getter methods are as below
setSelectedAgentData(selectedTableRowData){
this.selectedData = selectedTableRowData;
}
getSelectedAgentData(){
return this.selectedData;
}
Component - 2:
ngOnInit() {
this.userDetails = this.agentsService.getSelectedAgentData();
this.roles = this.agentsService.getRolesList();
}
Here the selectedData value is an empty object when I call the method this.agentsService.getSelectedAgentData() from component -2
Any help would be appreciated.
You can use Subject (rxjs library) for this purpose. So Subject can generate data on the one hand. And on the other hand, you can subscribe to changes in any place.
You service would look like this:
#Injectable()
export class YourService {
public selectedData: Subject<any>;
constructor() {
this.selectedData = new Subject();
}
generateSelectedAgentData(row: string) {
this.selectedData.next(row);
}
}
In your first Component:
makeUser(row){
this.agentsService.generateSelectedAgentData(row);
const mrf = this.modalService.open(MakeUserComponent);
}
In your second Component:
constructor(private ys: YourService){
this.ys.selectedData.subscribe(
data => {
console.log(data);
this.userDetails = data;
});
}
I'd like to map an object with properties (key) to a decorator (value). I'd like to use a Weak Map if possible. I have a solution that is working using a string, which is fine except that Weak Maps do not accept strings as keys. Is this possible with a Map or a WeakMap?
'use strict';
class Accordion {
constructor() {}
}
let Decorators = new Map();
Decorators.set({nodeName: 'tag-name-here', component: 'accordion'}, (client) => { return new Accordion(client) });
class Client {
constructor() {
let key = {nodeName: 'tag-name-here', component: 'accordion'}
let decorator;
if (Decorators.has(key)) {
decorator = Decorators.get(key)(this);
}
console.log(decorator); //undefined, unless I use a string as a key.
}
}
new Client();
It does not work because different instance of key: {nodeName: 'tag-name-here', component: 'accordion'} will map to a new memory location each time, so you won't be able to get your desired value this way. To make it work, you have to set it to a new variable, so that your code looks like the following:
'use strict';
class Accordion {
constructor() {}
}
let Decorators = new Map();
const key = {nodeName: 'tag-name-here', component: 'accordion'};
Decorators.set(key, (client) => { return new Accordion(client) });
class Client {
constructor() {
let decorator;
if (Decorators.has(key)) {
decorator = Decorators.get(key)(this);
}
console.log(decorator); // this should return an object
}
}
new Client();
I've got this EventsStorage typescript class that is responsible for storing and retrieving Event objects in ionic-storage (wrapper for sqlite and indexedDB). It uses my Event class throughout.
I would like to reuse a lot of this logic for something other than an Event, like a Widget.
I come from a ruby background where it would be relatively simple to extract all the storage logic, set a ruby var that is literally the class Event and use that var wherever I use Event. Can I do something similar in typescript? Is there another mechanic I can use to reuse the bulk of this class for something else, like Widget?
Ideally, my EventsStorage class becomes really lightweight, and I'm not just wrapping calls to this.some_storage_module.get_ids() or this.some_storage_module.insert_new_objs() -- which would have to be copy/pasted to every other instance I needed this.
Something like this:
export class EventsStorage { // extends BaseStorage (maybe??)
constructor(){
super(Event, 'events'); // or some small set of magical args
}
}
Here's the existing class:
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { Event } from '../classes/event';
// EventsStorage < EntityStorage
// - tracks local storage info
// - a key to an array of saved objects
// - a query() method that returns saved objects
#Injectable()
export class EventsStorage {
base_key: string;
ids_key: string;
constructor(
private storage: Storage
){
this.base_key = 'event';
this.ids_key = [this.base_key, 'ids'].join('_');
}
get_ids(): Promise<any>{
return this.storage.ready().then(() => {
return this.storage.get(this.ids_key).then((val) => {
if(val === null){
return [];
} else {
return val;
}
});
});
}
insert_new_objs(new_objs: any): Promise<any>{
return new_objs.reduce((prev: Promise<string>, cur: any): Promise<any> => {
return prev.then(() => {
return this.storage.set(cur._id, cur.event);
});
}, Promise.resolve()).then(() => {
console.log('saving event_ids');
return this.storage.set(this.ids_key, new_objs.map(obj => obj._id));
});
}
update(events: Event[]): Promise<any> {
let new_objs = events.map((event) => {
return {
_id: [this.base_key, event.id].join('_'),
event: event
};
});
return this.insert_new_objs(new_objs);
}
query(): Promise<Event[]>{
let events = [];
return this.get_ids().then((ids) => {
return ids.reduce((prev: Promise<string>, cur: string): Promise<any> => {
return prev.then(() => {
return this.get_id(cur).then((raw_event) => {
events = events.concat([raw_event as Event]);
return events;
});
});
}, Promise.resolve());
});
}
get_id(id: string): Promise<Event>{
return this.storage.get(id).then((raw_event) => {
return raw_event;
});
}
}
It looks to me like you want to use generics. You basically define some basic interface between all the things you'll want to store, and your code should depend on that interface. In your code as far as I can tell you only use the id property.
So it would look kinda like this
import { Event } from '...';
import { Widget } from '...';
interface HasId{
id: string;
}
class ItemsStorage<T extends HasId> {
....
get_id(id: string): Promise<T>{
...
}
}
const EventStorage = new ItemsStorage<Events>(storage);
const WidgetStorage = new ItemsStorage<Widget>(storage);
const ev = EventStorage.get_id('abc'); //type is Promise<Event>
const wd = WidgetStorage.get_id('def'); //type is Promise<Widget>
You can read more about generics here.
Edit:
1 - about subclassing - It's usually less preferable. If your ItemsStorage class need different behavior when dealing with Events vs Widgets, than subclassing is your solution. But if you have the same behavior for every class, one might call your code generic, and using generics is better.