Skip to content

Instantly share code, notes, and snippets.

@tihuan
Last active April 18, 2017 15:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tihuan/8f1ede025a119b098c1e to your computer and use it in GitHub Desktop.
Save tihuan/8f1ede025a119b098c1e to your computer and use it in GitHub Desktop.
Angular2 + RxJS Feedback
  1. Need more complex examples - Say, a two model app with multiple instances of each model and interaction/communication between the models.

E.g., SelfDrivingCar/Passenger - 2 cars, 10 passengers, where a passenger can only be in 1 car at a time, and a car takes up to 5 passengers, display cars and their passengers dynamically (pick up/drop off/passenger change car/passenger change car seat), etc..

This is because we find that simple Todo List is not complex enough to help us see more detailed implementation for a larger app, where you can easily get multiple models interacting with each other through streams. E.g., tracking which car a passenger belongs to, which seat a passenger takes, etc..

  1. Event handler (click, keyup, etc.) - when to use stream, when not to use stream? I see that currently in the tutorial, click handlers are not streams. But I know rx.angular.js actually provides an example on turning click event into a stream by using $createObservableFunction.

  2. Observable data flow: When orchestrating data flow among components, when do we use services (that have streams) and when do we use property binding?

  3. Data architecture: MV*, Flux (Redux), or Reactive Programming is its own data architecture?

  4. We think it'd be helpful if we could somehow come up with a style guide or a list of best practices to help developers think Rx in ng2.

E.g.,

  • For best practices:

In ng-book pg.169

addMessage(newMessage: Message) {
updates.next((messages: Message[]): Message[] => {
  return messages.concat(newMessage);
})
}

is actually bad practice, because then addMessage action is not composable with other streams. But I feel lots of developers will make this mistake, so perhaps we could have a section of 'common mistakes' too.

  • For style guide:

How to structure and organize code, so we can easily see which streams a stream subscribes to.

# code/rxjs/chat/app/ts/services/MessagesService.ts

import {Injectable, bind} from 'angular2/core';
import {Subject, Observable} from 'rxjs';
import {User, Thread, Message} from '../models';

let 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.updates subscribes to create

  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); // <----------- this.updates subscribes to markThreadAsRead

}

// an imperative function call to this action stream
addMessage(message: Message): void {
  this.newMessages.next(message);
}

messagesForThreadUser(thread: Thread, user: User): Observable<Message> {
  return this.newMessages
    .filter((message: Message) => {
             // belongs to this thread
      return (message.thread.id === thread.id) &&
             // and isn't authored by this user
             (message.author.id !== user.id);
    });
}
}

export var messagesServiceInjectables: Array<any> = [
bind(MessagesService).toClass(MessagesService)
];

In this service (from ng-book2 chat app), I personally feel the way this.updates subscribes to create and markThreadAsRead streams is messy. Because it happens at where other streams may (potentially) subscribe to other streams too. So, if I only want to find out the streams this.updates subscribe to, I'd need to scroll through the whole constructor function to find them. And if multiple developers worked on this file, it could get out of hand quickly.

I hope there's some guideline on a more structured way to:

  1. create streams
  2. subscribe to streams

either perhaps through wrapping things in functions then invoke them in the constructor function or put them in different files, or both, etc..

It seems like learning ng2 and RxJS separately is fine, and there are enough resources to help on that matter. But combining the two is a new territory and not enough people are discussing best practices yet. So it'd be great if we could have a community to start this conversation!

Finally, thanks for giving us the opportunity to share our thoughts with the Angular Team. We're very lucky to be part of this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment