{"id":282769,"date":"2016-12-20T14:10:03","date_gmt":"2016-12-20T11:10:03","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=282769"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=282769","title":{"rendered":"Angular2 + Websocket + RxJS + Rails5"},"content":{"rendered":"<p><img decoding=\"async\" src=\"http:\/\/swagger.websters.com.ua\/cover.jpg\" alt=\"image\"\/><\/p>\n<p>  \u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Angular2 \u0441 Rails 5 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Websocket.<br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<h2>Rails<\/h2>\n<p>  \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 ApplicationCable::Channel: <\/p>\n<pre><code class=\"ruby\">class ChatChannel &lt; ApplicationCable::Channel   def subscribed     stream_from 'chat_channel'   end    def index     ActionCable.server.broadcast 'chat_channel', { messages: Message.serialize_all( Message.all ) }   end    def create( data )     Message.create( name: data[ 'name' ], text: data[ 'text' ] );     ActionCable.server.broadcast 'chat_channel', { messages: Message.serialize_all( Message.all ) }   end    def unsubscribed   end end<\/code><\/pre>\n<h2>Angular2<\/h2>\n<p>  <\/p>\n<h3>WebSocketService<\/h3>\n<p>  \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431\u043c\u0435\u043d\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c:<\/p>\n<pre><code>import { Injectable }                           from &quot;@angular\/core&quot;; import { Subject, Observable, Subscription }    from 'rxjs\/Rx'; import { WebSocketSubject }                     from &quot;rxjs\/observable\/dom\/WebSocketSubject&quot;;  @Injectable() export class WebSocketService {      private ws: WebSocketSubject&lt;Object&gt;;     private socket: Subscription;     private url: string;      public message: Subject&lt;Object&gt; = new Subject();     public opened: Subject&lt;boolean&gt; = new Subject();      public close():void{         this.socket.unsubscribe();         this.ws.complete();     }      public sendMessage( message:string ):void{         this.ws.next( message );     }      public start( url: string ):void{         let self = this;          this.url = url;          this.ws = Observable.webSocket( this.url );          this.socket = this.ws.subscribe( {                          next: ( data:MessageEvent ) =&gt; {                 if( data[ 'type' ] == 'welcome' ){                     self.opened.next( true );                 }                 this.message.next( data );             },             error: () =&gt; {                  self.opened.next( false );                 this.message.next( { type: 'closed' } );                  self.socket.unsubscribe();                  setTimeout( () =&gt; {                     self.start( self.url );                 }, 1000 );              },             complete: () =&gt; {                 this.message.next( { type: 'closed' } );             }                      } );              }      } <\/code><\/pre>\n<p>  \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0435\u0441\u0442\u044c 3 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u0438 2 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 3 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<pre><code class=\"ruby\"> private ws: WebSocketSubject&lt;Object&gt;; private socket: Subscription; private url: string; <\/code><\/pre>\n<p>  ws \u2014 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f WebSocketSubject.<br \/>  socket \u2014 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 ws.<br \/>  url \u2014 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0441\u043e\u043a\u0435\u0442.<\/p>\n<pre><code class=\"ruby\"> public message: Subject&lt;Object&gt; = new Subject(); public opened: Subject&lt;boolean&gt; = new Subject(); <\/code><\/pre>\n<p>  message \u2014 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0441\u043e\u043a\u0435\u0442\u0430.<br \/>  opened \u2014 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043b\u0435\u0434\u0438\u0442 \u0437\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\u043c\/\u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u0441\u043e\u043a\u0435\u0442\u043e\u043c.<\/p>\n<pre><code class=\"ruby\"> public close():void{     this.socket.unsubscribe();     this.ws.complete(); } <\/code><\/pre>\n<p>  \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u0441\u043e\u043a\u0435\u0442\u0430.<\/p>\n<pre><code class=\"ruby\"> public sendMessage( message:string ):void{     this.ws.next( message ); } <\/code><\/pre>\n<p>  \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0441\u043e\u043a\u0435\u0442.<\/p>\n<pre><code class=\"ruby\"> public start( url: string ):void{     let self = this;      this.url = url;      this.ws = Observable.webSocket( this.url );      this.socket = this.ws.subscribe( {          next: ( data:MessageEvent ) =&gt; {             if( data[ 'type' ] == 'welcome' ){                 self.opened.next( true );             }             this.message.next( data );         },         error: () =&gt; {              self.opened.next( false );             this.message.next( { type: 'closed' } );              self.socket.unsubscribe();              setTimeout( () =&gt; {                 self.start( self.url );             }, 1000 );          },         complete: () =&gt; {             this.message.next( { type: 'closed' } );         }      } );  } <\/code><\/pre>\n<p>  \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0441\u043e\u043a\u0435\u0442\u043e\u043c, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044f \u0435\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044e \u0438\u0437 \u043d\u0435\u0435. \u041f\u0440\u0438 \u043f\u043e\u0442\u0435\u0440\u0435 \u0441\u0432\u044f\u0437\u0438 \u043a\u0430\u0436\u0434\u0443\u044e \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u044f\u0437\u044c.<\/p>\n<h3>ChannelWebsocketService<\/h3>\n<p>  \u041d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u044b Rails5 Action Cable:<\/p>\n<pre><code class=\"ruby\"> import { Injectable }              from &quot;@angular\/core&quot;; import { Subject }                 from &quot;rxjs\/Subject&quot;;  import { WebSocketService }        from &quot;.\/websocket.service&quot;;  @Injectable() export class ChannelWebsocketService {      private socketStarted: boolean;      public observableData: Subject&lt;Object&gt; = new Subject();     public identifier:Object = {};     public identifierStr: string;     public subscribed: Subject&lt;boolean&gt; = new Subject();      constructor( private websocketService: WebSocketService ){          this.observeOpened();         this.observeMessage();      }      private static encodeIdentifier( identifier:string ):Object{          return JSON.parse( identifier );      }      private static getDataString( parameters:Object ):string{          let first = true,             result = '';          for ( let key in parameters ){              if( first ){                  first = false;                 result +=  `\\&quot;${ key }\\&quot;:\\&quot;${ parameters[ key ] }\\&quot;`;              } else {                  result += `, \\&quot;${ key }\\&quot;:\\&quot;${ parameters[ key ] }\\&quot;`;              }          }          return `{ ${ result } }`;      }      private getSubscribeString():string{          this.identifierStr = ChannelWebsocketService.getDataString( this.identifier );          return JSON.stringify( {             command: 'subscribe',             identifier: this.identifierStr         } );      };      private isThisChannel( data:Object ):boolean {          if( data[ 'identifier' ] ){              let identifier = ChannelWebsocketService.encodeIdentifier( data[ 'identifier' ] );              if ( JSON.stringify( identifier ) === JSON.stringify( this.identifier ) ){                  return true;              }          }          return false;      }      private observeMessage(){          let self = this;          this.websocketService.message.subscribe( ( data: Object ) =&gt; {              if( self.isThisChannel( data ) ){                  if( data[ 'type' ] && data[ 'type' ] == 'confirm_subscription' ){                      this.subscribed.next( true );                  } else if ( data[ 'message' ] ){                      this.observableData.next( data[ 'message' ] );                  }              }          } );      }      private observeOpened(){          let self = this;          this.websocketService.opened.subscribe( ( data: boolean ) =&gt; {              self.socketStarted = data;              if( data ){                 self.subscribe();             }          } );      }          private subscribe(){          this.websocketService.sendMessage( this.getSubscribeString() );      }           public send( data: Object ){          this.websocketService.sendMessage( JSON.stringify( {             command:'message',             identifier: this.identifierStr,             data: ChannelWebsocketService.getDataString( data )         } ) );      }      public unsubscribe(){          this.websocketService.sendMessage( JSON.stringify( {             command: 'unsubscribe',             identifier: this.identifierStr } ) );         this.subscribed.next( false );      }  } <\/code><\/pre>\n<p>  \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0435\u0441\u0442\u044c 2 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u0438 3 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 7 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 \u0438 2 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<pre><code class=\"ruby\"> private socketStarted: boolean; private identifierStr: string; <\/code><\/pre>\n<p>  socketStarted \u2014 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0441\u043e\u043a\u0435\u0442.<br \/>  identifierStr \u2014 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f Rails5 Action Cable \u043a\u0430\u043d\u0430\u043b\u0430.<\/p>\n<pre><code class=\"ruby\"> public observableData: Subject&lt;Object&gt; = new Subject(); public identifier:Object = {}; public subscribed: Subject&lt;boolean&gt; = new Subject(); <\/code><\/pre>\n<p>  observableData \u2014 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043e\u043a\u0435\u0442\u0430 \u0434\u043b\u044f \u043a\u0430\u043d\u0430\u043b\u0430.<br \/>  identifier \u2014 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f Rails5 Action Cable \u043a\u0430\u043d\u0430\u043b\u0430.<br \/>  subscribed \u2014 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438.<\/p>\n<pre><code class=\"ruby\"> constructor( private websocketService: WebSocketService ){      this.observeOpened();     this.observeMessage();  }  ...  private observeMessage(){      let self = this;      this.websocketService.message.subscribe( ( data: Object ) =&gt; {          if( self.isThisChannel( data ) ){              if( data[ 'type' ] && data[ 'type' ] == 'confirm_subscription' ){                  this.subscribed.next( true );              } else if ( data[ 'message' ] ){                  this.observableData.next( data[ 'message' ] );              }          }      } );  }  private observeOpened(){      let self = this;      this.websocketService.opened.subscribe( ( data: boolean ) =&gt; {          self.socketStarted = data;          if( data ){             self.subscribe();         }      } );  } <\/code><\/pre>\n<p>  \u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043c\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c 2 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: observeMessage \u0438 observeOpened, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u044e\u0442 \u043f\u0440\u0438\u0441\u043b\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u043a\u0435\u0442\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u043e\u043a\u0435\u0442\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e.<\/p>\n<pre><code class=\"ruby\"> private static encodeIdentifier( identifier:string ):Object{      return JSON.parse( identifier );  }  private static getDataString( parameters:Object ):string{      let first = true,         result = '';      for ( let key in parameters ){          if( first ){              first = false;             result +=  `\\&quot;${ key }\\&quot;:\\&quot;${ parameters[ key ] }\\&quot;`;          } else {              result += `, \\&quot;${ key }\\&quot;:\\&quot;${ parameters[ key ] }\\&quot;`;          }      }      return `{ ${ result } }`;  } <\/code><\/pre>\n<p>  encodeIdentifier \u2014 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0432\u0435\u0440\u043d\u0443\u043b \u0441\u043e\u043a\u0435\u0442 \u0434\u043b\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043d\u0430 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u043a \u043a\u0430\u043d\u0430\u043b\u0443.<br \/>  getDataString \u2014 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442 \u0441\u0442\u0440\u043e\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 Rails5 Action Cable.<\/p>\n<pre><code class=\"ruby\"> private getSubscribeString():string{      this.identifierStr = ChannelWebsocketService.getDataString( this.identifier );      return JSON.stringify( {         command: 'subscribe',         identifier: this.identifierStr     } );  }; <\/code><\/pre>\n<p>  \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b Rails5 Action Cable.<\/p>\n<pre><code class=\"ruby\"> private isThisChannel( data:Object ):boolean {      if( data[ 'identifier' ] ){          let identifier = ChannelWebsocketService.encodeIdentifier( data[ 'identifier' ] );          if ( JSON.stringify( identifier ) === JSON.stringify( this.identifier ) ){              return true;          }      }      return false;  } <\/code><\/pre>\n<p>  \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442 \u0441\u043e\u043a\u0435\u0442\u0430 \u043a \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043a\u0430\u043d\u0430\u043b\u0443.<\/p>\n<pre><code class=\"ruby\">  private subscribe(){      this.websocketService.sendMessage( this.getSubscribeString() );  } <\/code><\/pre>\n<p>  \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b.<\/p>\n<pre><code class=\"ruby\"> public send( data: Object ){      this.websocketService.sendMessage( JSON.stringify( {         command:'message',         identifier: this.identifierStr,         data: ChannelWebsocketService.getDataString( data )     } ) );  } <\/code><\/pre>\n<p>  \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043a\u0430\u043d\u0430\u043b Rails5 Action Cable;<\/p>\n<pre><code class=\"ruby\"> public unsubscribe(){      this.websocketService.sendMessage( JSON.stringify( {         command: 'unsubscribe',         identifier: this.identifierStr } ) );     this.subscribed.next( false );  } <\/code><\/pre>\n<p>  \u041e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043e\u0442 \u043a\u0430\u043d\u0430\u043b\u0430.<\/p>\n<h3>ChatChannelService<\/h3>\n<p>  C\u0435\u0440\u0432\u0438\u0441, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442 ChannelWebsocketService \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b ChatChannel:<\/p>\n<pre><code class=\"ruby\"> import { Injectable }                           from &quot;@angular\/core&quot;;  import { ChannelWebsocketService }    from &quot;.\/channel.websocket.service&quot;; import { WebSocketService }               from &quot;.\/websocket.service&quot;;  @Injectable() export class ChatChannelService extends ChannelWebsocketService {      constructor( websocketService: WebSocketService ){          super( websocketService );          this.identifier = {             channel: 'ChatChannel'         };      }  } <\/code><\/pre>\n<p>  \u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f identifier \u0434\u043b\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043a\u0430\u043d\u0430\u043b\u0430.<\/p>\n<h3>ChatComponent<\/h3>\n<p>  \u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f ChatChannelService \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442\/\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043a\u0430\u043d\u0430\u043b.<br \/>  \u041f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0434\u0430 \u043d\u0435 \u043f\u0440\u0438\u0432\u043e\u0436\u0443, \u043e\u043d \u0435\u0441\u0442\u044c \u0432 GitHub, \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0430 \u043d\u0438\u0436\u0435<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440<\/h2>\n<p>  <a href=\"https:\/\/github.com\/Scorpionsc\/Angular2-RxJS-Websocket-Rail5\">\u0417\u0434\u0435\u0441\u044c<\/a> \u043c\u043e\u0436\u043d\u043e \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440.<\/p>\n<p>  \u0414\u043b\u044f \u0441\u0442\u0430\u0440\u0442\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043f\u0430\u043f\u043a\u0443 \u00abclient\u00bb \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c:<\/p>\n<pre><code>npm install gulp <\/code><\/pre>\n<p>  \u041d\u0430\u0434\u0435\u044e\u0441\u044c \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0435.<br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habrahabr.ru\/post\/318040\/\"> https:\/\/habrahabr.ru\/post\/318040\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p><img decoding=\"async\" src=\"http:\/\/swagger.websters.com.ua\/cover.jpg\" alt=\"image\"\/><\/p>\n<p>  \u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Angular2 \u0441 Rails 5 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Websocket.  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-282769","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/282769","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=282769"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/282769\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=282769"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=282769"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=282769"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}