Topic:
md-table ui that implements the cdk-table in Angular Material 2
Problem:
unable to get connect to emit after a user-invoked http call returns a response
Approach:
create a hot observable out of a behavior subject in a service. parent component invokes a method in the service that feeds an array of objects into the behavior subject. the child component subscribes to the behavior subject's hot observable in it's constructor. the child component recreates the reference to the datasource in the subscription method with the newly received array of objects
Expected Behavior:
each time the behavior subject is fed new data via .next(), connect should fire
Observed Behavior:
the connect method fires only on initialization of the child component
Parent Component:
import { Component } from '@angular/core';
import { InboundMessagesService } from '../messages/services/inbound/inbound.service';
import { Message } from '../messages/model/message';
@Component({
selector: 'search-bar',
templateUrl: './search-bar.component.html',
styleUrls: [ './search-bar.component.css'],
providers: [ InboundMessagesService ]
})
export class SearchBarComponent {
hac: string = "";
constructor( private inboundMessagesService: InboundMessagesService ) { }
onSubmit( event: any ): void {
this.hac = event.target.value;
this.inboundMessagesService.submitHac( this.hac );
}
}
Service:
import { Injectable } from '@angular/core';
import { Headers,
Http,
RequestMethod,
RequestOptions,
Response } from '@angular/http';
import { HttpErrorResponse } from "@angular/common/http";
import { Observable } from 'rxjs/Rx';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Subscription } from 'rxjs/Subscription';
import "rxjs/add/operator/mergeMap";
import { Message } from '../../model/message';
import { LookupService } from '../../../lookup/lookup.service';
@Injectable()
export class InboundMessagesService {
dataChange: BehaviorSubject<Message[]> = new BehaviorSubject<Message[]>([]);
dataChangeObservable = Observable.from( this.dataChange ).publish();
messages: Message[];
get data(): Message[] {
return this.dataChange.value;
}
baseUrl: string = 'http://foobar/query?parameter=';
headers = new Headers();
options = new RequestOptions({ headers: this.headers });
response: Observable<Response>;
constructor( private http: Http,
private lookupService: LookupService ) {
console.log( "inboundService constructor - dataChange: ", this.dataChange );
this.dataChangeObservable.connect()
}
submitHac( hac: string ) {
console.log( "submitHac received: ", hac );
this.getMessages( hac )
.subscribe( ( messages: any ) => {
this.dataChange.next( messages )
}),
( err: HttpErrorResponse ) => {
if ( err.error instanceof Error ) {
// A client-side or network error occurred. Handle it accordingly.
console.log( 'An error occurred:', err.error.message );
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.log( `Backend returned code ${ err.status }, body was: ${ err.error }` );
console.log( "full error: ", err );
}
};
}
getMessages( hac: string ) {
console.log( "inboundService.getMessages( hac ) got: ", hac );
return this.lookupService
.getMailboxUuids( hac )
.switchMap(
( mailboxUuidsInResponse: Response ) => {
console.log( "lookup service returned: ", mailboxUuidsInResponse );
return this.http.get( this.baseUrl + mailboxUuidsInResponse.json(), this.options )
})
.map(
( messagesInResponse: any ) => {
console.log( "request returned these messages: ", messagesInResponse );
messagesInResponse.forEach(
(message: any ) => {
this.messages.push(
this.createMessage( message )
)});
return this.messages;
})
}
createMessage( message: any ): Message {
return new Message(
message.name,
message.type,
message.contentType,
message.state,
message.source,
message.target,
message.additionalData
)
}
}
Child Component:
import { Component } from '@angular/core';
import { HttpErrorResponse } from "@angular/common/http";
import { DataSource, CdkTable } from '@angular/cdk';
import { Observable } from 'rxjs/Observable';
import { Message } from '../../../messages/model/message';
import { InboundMessagesService } from '../../../messages/services/inbound/inbound.service';
import { SearchBarComponent } from '../../../search_bar/search-bar.component';
@Component({
selector: 'inbound-messages',
templateUrl: './../inbound-messages.component.html',
styleUrls: [
'app/mailboxes/mailboxes-layout.css',
'./../inbound-messages.component.css'
],
providers: [ InboundMessagesService ]
})
export class InboundMessagesComponent {
dataSource: InboundDataSource | null;
displayedColumns = [ 'name', 'type', 'contentType', 'state', 'source', 'target', 'additionalData' ];
constructor( private inboundMessagesService: InboundMessagesService ) {
console.log( "inbound component constructor (this): ", this );
this.inboundMessagesService.dataChangeObservable.connect();
}
ngOnInit() {
console.log( "inbound component ngOnInit()" );
this.dataSource = new InboundDataSource( this.inboundMessagesService );
}
}
export class InboundDataSource extends DataSource<Message> {
constructor( private inboundMessagesService: InboundMessagesService ) {
super();
console.log( "InboundDataSource constructor" );
}
connect(): Observable<Message[]> {
console.log( "CONNECT called" );
return this.inboundMessagesService.dataChangeObservable
}
disconnect() {}
}
this.dataChange.next(messagesObservable.switch())
. In your lastflatMap
ingetMessages
you should justreturn messages
which will remove the need forswitch()
. Then you should domessagesObservable.subscribe(messages => {this.dataChange.next(messages);});
Thenext()
method is supposed to take in an item, not a stream. In this case it should be receiving aMessage[]
. However, you are passing inObservable<Message[]>
. – PacegetMessages()
. The firstflatMap
should beswitchMap
. And the secondflatMap
should just bemap
since it is returning new values, not a new Observable. – Will Howell