i have a problem with a template i am using in angular 4.
This template implement a notification system, where you can add new notifications, but the documentation do not specify how one can delete the elements of the observer ReplaySubject.
The template implement this as a service as follows:
private notificationsList: Notification[] = [];
// a stream that publishes new notifications only once
public newNotifications: Subject<Notification> = new Subject<Notification>();
// `notifications` is a stream that emits an array of the most up to date notifications
public notifications: ReplaySubject<Notification[]> =
new ReplaySubject<Notification[]>(1);
// `updates` receives _operations_ to be applied to our `notifications`
// it's a way we can perform changes on *all* notifications (that are currently
// stored in `notifications`)
public updates: Subject<any> = new Subject<any>();
// action streams
public create: Subject<Notification> = new Subject<Notification>();
// public markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
// recois des operation, et les fais sur la liste interne, puis diffuse le
// resultat sur notifications
this.updates.subscribe((ope) => {
this.notificationsList = ope(this.notificationsList);
console.log(this.notificationsList);
this.notifications.next(this.notificationsList);
});
this.newNotifications
.map(function(notification: Notification): INotificationsOperation {
return (notifications: Notification[]) => {
return notifications.concat(notification);
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
public addNotification(notification: Notification): void {
this.newNotifications.next(notification);
}
I try to ask to the owner how i can delete an actual element of the notification list, but he just tell me that i can access the "notifications" subject to receive the last version of it. But do not mention how i can actually delete an element of the list.
Some one know something about?
Thanks!
I added a public function that you can use.
I added a comment to let you see which part of the code you can modify if you want to delete elements by name for example, or don't want to resize the list.
Explanation at the end of my post.
private notificationsList: Notification[] = [];
// a stream that publishes new notifications only once
public newNotifications: Subject<Notification> = new Subject<Notification>();
public removeNotificationByIndex$ : Subject<number> = new Subject<number>();
// `notifications` is a stream that emits an array of the most up to date notifications
public notifications: ReplaySubject<Notification[]> =
new ReplaySubject<Notification[]>(1);
// `updates` receives _operations_ to be applied to our `notifications`
// it's a way we can perform changes on *all* notifications (that are currently
// stored in `notifications`)
public updates: Subject<any> = new Subject<any>();
// action streams
public create: Subject<Notification> = new Subject<Notification>();
// public markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
// recois des operation, et les fais sur la liste interne, puis diffuse le
// resultat sur notifications
this.updates.subscribe((ope) => {
this.notificationsList = ope(this.notificationsList);
console.log(this.notificationsList);
this.notifications.next(this.notificationsList);
});
this.newNotifications
.map(function(notification: Notification): INotificationsOperation {
return (notifications: Notification[]) => {
return notifications.concat(notification);
};
})
.subscribe(this.updates);
this.removeNotificationByIndex$
.map(function(index: number){
return (notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
public addNotification(notification: Notification): void {
this.newNotifications.next(notification);
}
// delete the element in the "index" position of the list.
// /!\ Resizes the list
public removeNotificationsByIndex(index: number): void {
this.removeNotificationByIndex$.next(index);
}
What are the changes ?
public removeNotificationByIndex$ : Subject<number> = new Subject<number>();
This subject will receive (asynchronously) an index, and trigger a process using this index.
this.removeNotificationByIndex$
.map(function(index: number){
return (notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
})
.subscribe(this.updates);
When the index is emitted (i.e you use the associated imperative function), a function (ES6 arrow function) is generated from it. This is it :
(notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
This function is passed to this.update, which will apply it. In this context, ope is this function. when received, this.notificationList is modified as follow :
this.notificationsList = ope(this.notificationsList);
Finally, this new list is published to the ReplaySubject notifications:
this.notifications.next(this.notificationsList);
Which propagate this new list to all its subscribers.
VoilĂ :). Good luck !
Related
I am new to Angular and RxJS. I am analysing the following extract from the Angular tutorial (ng-book2-book-angular-11-r77-code).
My question is when the call this.messages = this.updates... (in the constructor method) executes - is it executing in the constructor or when addMessage (message: Message) is called?
import { Injectable } from '#angular/core';
import { Subject, Observable } from 'rxjs';
import { User } from '../user/user.model';
import { Thread } from '../thread/thread.model';
import { Message } from '../message/message.model';
const initialMessages: Message[] = [];
interface IMessagesOperation extends Function {
(messages: Message[]): Message[];
}
#Injectable()
export class MessagesService {
// a stream that publishes new messages only once
newMessages: Subject<Message> = new Subject<Message>();
// `messages` is a stream that emits an array of the most up to date messages
messages: Observable<Message[]>;
// `updates` receives _operations_ to be applied to our `messages`
// it's a way we can perform changes on *all* messages (that are currently
// stored in `messages`)
updates: Subject<any> = new Subject<any>();
// action streams
create: Subject<Message> = new Subject<Message>();
markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
this.messages = this.updates
// watch the updates and accumulate operations on the messages
.scan((messages: Message[],
operation: IMessagesOperation) => {
return operation(messages);
},
initialMessages)
// make sure we can share the most recent list of messages across anyone
// who's interested in subscribing and cache the last known list of
// messages
.publishReplay(1)
.refCount();
// `create` takes a Message and then puts an operation (the inner function)
// on the `updates` stream to add the Message to the list of messages.
//
// That is, for each item that gets added to `create` (by using `next`)
// this stream emits a concat operation function.
//
// Next we subscribe `this.updates` to listen to this stream, which means
// that it will receive each operation that is created
//
// Note that it would be perfectly acceptable to simply modify the
// "addMessage" function below to simply add the inner operation function to
// the update stream directly and get rid of this extra action stream
// entirely. The pros are that it is potentially clearer. The cons are that
// the stream is no longer composable.
this.create
.map( function(message: Message): IMessagesOperation {
return (messages: Message[]) => {
return messages.concat(message);
};
})
.subscribe(this.updates);
this.newMessages
.subscribe(this.create);
// similarly, `markThreadAsRead` takes a Thread and then puts an operation
// on the `updates` stream to mark the Messages as read
this.markThreadAsRead
.map( (thread: Thread) => {
return (messages: Message[]) => {
return messages.map( (message: Message) => {
// note that we're manipulating `message` directly here. Mutability
// can be confusing and there are lots of reasons why you might want
// to, say, copy the Message object or some other 'immutable' here
if (message.thread.id === thread.id) {
message.isRead = true;
}
return message;
});
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
addMessage(message: Message): void {
this.newMessages.next(message);
}
}
In the following example, lazyNumber is assigned in the constructor and referenced in the printNumber method.
The expression isn't evaluated to 4 until it's called.
class a {
constructor(){
this.lazyNumber = () => 5 - 1;
}
printNumber(){
console.log( this.lazyNumber() );
}
}
The same fundamental thing is happening in your example.
You example is defining an attribute and the code that get runs when it's "called". addMessage is emitting a new value on the observable, that causes the listeners assinged above to react accordingly
I already have setup my websocket connection with pusher. I can fire events at the websocket admin and i can show the output of it via console.log. Now i created a new event that if the user adds new product, the table will be updated whenever who is viewing it. But it seems I can add data successfully but the table wont update to other user. Can someone know why my event is not working?
ProductsEvent.php
namespace App\Events;
//show only important imports
use Illuminate\Broadcasting\Channel;
use App\Product; //Import my model
class ProductsEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $product;
public function __construct(Product $product)
{
$this->product = $product;
}
public function broadcastOn()
{
return new Channel('Products');
}
}
ProductsControlller (store)
public function store(Request $request)
{
$product = new Product;
//some validation...
//broadcast(new ProductsEvent($product)); if i put it here i got No query results for model [App\Product].
$product->barcode = $request->barcode;
$product->product_name = $request->product_name;
$product->price = $request->price;
$product->quantity = 0;
$product->category = $request->category;
$product->supplier_id = $request->supplier;
$product->save();
broadcast(new ProductsEvent($product));
}
channels.php
Broadcast::channel('Products',function(){
return true;
});
and my vue component
created(){
//when i successfully created the data,
i will call getProducts to listen in the channel for DOM updates
Echo.join('Products')
.listen('ProductsEvent',(event)=>{
this.getProducts()
})
}
If i call broadcast before save in my controller, I got something like this
No query results for model [App\Product].
I uncomented the App\Providers\BroadcastServiceProvider::class, line in config.php in order for the broadcast to work.
I dont know why .join wont work but I used window.Echo.channel i doubt this is the right thing to do.
created(){
this.getProducts()
this.getSuppliers()
// Echo.join('Products')
// .listen('ProductsEvent',(event)=>{
// // this.products.push(event.products)
// this.getProducts()
// })
// .here(()=>{
// console.log('here')
// })
window.Echo.channel('Products').listen('ProductsEvent',(e)=>{
this.getProducts()
toastr.success('Product Updated')
})
}
I have a sandbox which subscribes to a stream of messages and I want to filter that stream to find messages that have been sent to or received from a specific user using route params specified in another component.
messages.sandbox.ts:
messages$: Observable<Array<Message>> = this.store.select(state => state.data.messages);
fetchReceived(id: string): Observable<Array<Message>> {
return this.messages$.map((messages: any) => {
return messages.filter((message: Message) => {
return message.recipientId == id;
});
});
}
fetchSent(id: string): Observable<Array<Message>> {
return this.messages$.map((messages: any) => {
return messages.filter((message: Message) => {
return message.userId == id;
})
})
}
messages.detail.container.ts
sentMessages$ = new Observable<Array<Message>>();
receivedMessages$ = new Observable<Array<Message>>();
matchingMessages$ = new Observable<Array<Message>>();
ngOnInit() {
this.route.params.subscribe((params: Params) => {
this.sentMessages$ = this.sb.fetchReceived(params['id']);
this.receivedMessages$ = this.sb.fetchSent(params['id']);
this.matchingMessages$ = Observable.merge(this.sentMessages$, this.receivedMessages$);
});
}
this.matchingMessages$ seems to only include this.receivedMessages$ however I know that this.sentMessages$ is not null as I can use it in my template without a problem.
Am I missing something with merging Observables? Would it be better to create a single fetchMessages method which filters for either the userId or recipientId equalling the route param id? If so how would I go about that?
Thanks!!
You have the right general idea. Just a few flaws.
Never use new Observable<T>(). It does not do what you think it does. It pretty much does not do anything useful. Always construct observables from factory methods or other observables
You need to transform the params observable into a new observable using an operator. Your problem is you subscribe to the params observable, and then construct new observables each time. But other code will have already subscribed to the initial observables so they will never see the changes.
So you want to do something like this:
sentMessages$ : Observable<Array<Message>>;
receivedMessages$ : Observable<Array<Message>>;
matchingMessages$ : Observable<Array<Message>>;
ngOnInit() {
const params$ = this.route.params;
// use switchMap to map the new params to a new sent observable
// each time params change, unsubscribe from the old fetch and subscribe
// to the new fetch. Anyone subscribed to "sentMessages" will see the
// change transparently
this.sentMessages$ = params$.switchMap((params: Params) => this.sb.fetchReceived(params['id']));
// same for received
this.receivedMessages$ = params$.switchMap((params: Params) => this.sb.fetchSent(params['id'])));
// merge the 2 streams together
this.matchingMessages$ = Observable.merge(this.sentMessages$, this.receivedMessages$);
}
Edit:
to answer your other question: is it better to create a single observable that matches senders and receivers: depends upon your use case. But here is how you could go about it:
messages.sandbox.ts:
fetchEither(id: string): Observable<Array<Message>> {
return this.messages$.map((messages: any) => {
return messages.filter((message: Message) => {
return message.recipientId == id || message.userId === id;
});
});
}
container:
matchingMessages$ : Observable<Array<Message>>;
ngOnInit() {
const params$ = this.route.params;
// use switchMap to map the new params to a new either observable
// each time params change, unsubscribe from the old and subscribe
// to the new fetch. Anyone subscribed to "matchingMessages" will see the
// change transparently
this.matchingMessages$ = params$.switchMap((params: Params) => this.sb.fetchEither(params['id']));
}
I am using rxjs together with Angular 2 and Typescript. I would like to share a common web-resource (a "project" in the context of my app, essentially a JSON document) between multiple components. To achieve this I introduced a service that exposes an observable, which will be shared by all clients:
/**
* Handed out to clients so they can subscribe to something.
*/
private _observable : Observable<Project>;
/**
* Used to emit events to clients.
*/
private _observer : Observer<Project>;
constructor(private _http: Http) {
// Create observable and observer once and for all. These instances
// are not allowed to changed as they are passed on to every subscriber.
this._observable = Observable.create( (obs : Observer<Project>) => {
this._observer = obs;
});
}
Clients now simply get a reference to that one _observable and subscribe to it.
/**
* Retrieves an observable that always points to the active
* project.
*/
get ActiveProject() : Observable<Project> {
return (this._observable);
}
When some component decides to actually load a project, it calls the following method:
/**
* #param id The id of the project to set for all subscribers
*/
setActiveProject(id : string) {
// Projects shouldn't change while other requests are in progress
if (this._httpRequest) {
throw { "err" : "HTTP request in progress" };
}
this._httpRequest = this._http.get('/api/project/' + id)
.catch(this.handleError)
.map(res => new Project(res.json()));
this._httpRequest.subscribe(res => {
// Cache the project
this._cachedProject = res;
// Show that there are no more requests
this._httpRequest = null;
// Inform subscribers
this._observer.next(this._cachedProject)
console.log("Got project");
});
}
It basically does a HTTP request, transforms the JSON document into a "proper" instance and calls this._observer.next() to inform all subscribers about the change.
But if something subscribes after the HTTP request has already taken place, the see nothing until a new HTTP request is issued. I have found out that there is some kind of caching (or replay?) mechanism in rxjs that seems to adress this, but I couldn't figure out how to use it.
tl;dr: How do I ensure that a call to subscribe on the observer initially receives the most recent value?
Extra question: By "pulling the observer out of the observable" (in the constructor), have I essentially created a subject?
That's what BehaviorSubject does
import { BehaviorSubject } from 'rxjs/subject/BehaviorSubject';
...
obs=new BehaviourSubject(4);
obs.subscribe(); //prints 4
obs.next(3); //prints 3
obs.subscribe(); //prints 3
I usually achieve this with shareReplay(1). Using this operator with 1 as parameter will ensure that the latest value emitted will be kept in a buffer, so when there is a new subscriber that value is immediately passed on to it. You can have a look at the documentation :
var interval = Rx.Observable.interval(1000);
var source = interval
.take(4)
.doAction(function (x) {
console.log('Side effect');
});
var published = source
.shareReplay(3);
published.subscribe(createObserver('SourceA'));
published.subscribe(createObserver('SourceB'));
// Creating a third subscription after the previous two subscriptions have
// completed. Notice that no side effects result from this subscription,
// because the notifications are cached and replayed.
Rx.Observable
.return(true)
.delay(6000)
.flatMap(published)
.subscribe(createObserver('SourceC'));
function createObserver(tag) {
return Rx.Observer.create(
function (x) {
console.log('Next: ' + tag + x);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
}
// => Side effect
// => Next: SourceA0
// => Next: SourceB0
// => Side effect
// => Next: SourceA1
// => Next: SourceB1
// => Side effect
// => Next: SourceA2
// => Next: SourceB2
// => Side effect
// => Next: SourceA3
// => Next: SourceB3
// => Completed
// => Completed
// => Next: SourceC1
// => Next: SourceC2
// => Next: SourceC3
// => Completed
Extra question: By "pulling the observer out of the observable" (in
the constructor), have I essentially created a subject?
I am not sure what you mean by that, but no. A subject is both an observer and an observable and have specific semantics. It is not enough to 'pull the observer out of the observable' as you say. For subjects semantics, have a look here : What are the semantics of different RxJS subjects?
I've looked in a variety of places for an answer to my query, but nothing has helped me thus far. I'm currently trying to learn Android development, and i'm stuck on how to sort a list alphabetically. I am using this tutorial on how to create the list and have modified parts of the "Albums" page to suit my needs, from albums to artists (i'm still working on this, just wanting the sorting finished before changing it fully). This particular list calls on a file from a HTTP address when the app is accessed to check it for updates.
Here is the code from that particular page, minus all the imports:
public class AlbumsActivity extends ListActivity {
// Connection detector
ConnectionDetector cd;
// Alert dialog manager
AlertDialogManager alert = new AlertDialogManager();
// Progress Dialog
private ProgressDialog pDialog;
// Creating JSON Parser object
JSONParser jsonParser = new JSONParser();
ArrayList<HashMap<String, String>> albumsList;
// albums JSONArray
JSONArray albums = null;
// albums JSON url
private static final String URL_ALBUMS = "http://api.androidhive.info/songs/albums.php";
// ALL JSON node names
private static final String TAG_ID = "id";
private static final String TAG_NAME = "name";
private static final String TAG_SONGS_COUNT = "songs_count";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_albums);
cd = new ConnectionDetector(getApplicationContext());
// Check for internet connection
if (!cd.isConnectingToInternet()) {
// Internet Connection is not present
alert.showAlertDialog(AlbumsActivity.this, "Internet Connection Error",
"Please connect to working Internet connection", false);
// stop executing code by return
return;
}
// Hashmap for ListView
albumsList = new ArrayList<HashMap<String, String>>();
// Loading Albums JSON in Background Thread
new LoadAlbums().execute();
// get listview
ListView lv = getListView();
/**
* Listview item click listener
* TrackListActivity will be lauched by passing album id
* */
lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View view, int arg2,
long arg3) {
// on selecting a single album
// TrackListActivity will be launched to show tracks inside the album
Intent i = new Intent(getApplicationContext(), TrackListActivity.class);
// send album id to tracklist activity to get list of songs under that album
String album_id = ((TextView) view.findViewById(R.id.album_id)).getText().toString();
i.putExtra("album_id", album_id);
startActivity(i);
}
});
}
/**
* Background Async Task to Load all Albums by making http request
* */
class LoadAlbums extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(AlbumsActivity.this);
pDialog.setMessage("Listing Artists...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* getting Albums JSON
* */
protected String doInBackground(String... args) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
String json = jsonParser.makeHttpRequest(URL_ALBUMS, "GET",
params);
// Check your log cat for JSON reponse
Log.d("Albums JSON: ", "> " + json);
try {
albums = new JSONArray(json);
if (albums != null) {
// looping through All albums
for (int i = 0; i < albums.length(); i++) {
JSONObject c = albums.getJSONObject(i);
// Storing each json item values in variable
String id = c.getString(TAG_ID);
String name = c.getString(TAG_NAME);
String songs_count = c.getString(TAG_SONGS_COUNT);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_ID, id);
map.put(TAG_NAME, name);
map.put(TAG_SONGS_COUNT, songs_count);
// adding HashList to ArrayList
albumsList.add(map);
}
}else{
Log.d("Albums: ", "null");
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String file_url) {
// dismiss the dialog after getting all albums
pDialog.dismiss();
// updating UI from Background Thread
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
ListAdapter adapter = new SimpleAdapter(
AlbumsActivity.this, albumsList,
R.layout.list_item_albums, new String[] { TAG_ID,
TAG_NAME, TAG_SONGS_COUNT }, new int[] {
R.id.album_id, R.id.album_name, R.id.songs_count });
// updating listview
setListAdapter(adapter);
}
});
}
}
}
My problem is that I have no real idea where I need to put the Collections.sort command. I have tried in so many places, but cannot get it working. No matter where I put the command, it always sorts in order of ID. This is the full code I have for that:
Collections.sort(params, new Comparator<NameValuePair>() {
#Override
public int compare(NameValuePair art1, NameValuePair art2) {
//here getTitle() method return app name...
return art1.getName().compareToIgnoreCase(art2.getName());
}
});
If I try to have return art1.name.compareToIgnoreCase(art2.name); it comes back with an error that says name cannot be resolved or is not a field. I'm really stumped. I thought a list might be a good way to start learning to develop for Android, but now i'm finding this incredibly hard.
Any help would be greatly appreciated.