given this saga:
#Saga
#Getter
#Slf4j
public class TasksForStateSaga {
#Autowired
transient CommandGateway commandGateway;
#Autowired
transient EventBus eventBus;
#Autowired
transient TaskService taskService;
Map<String, TaskStatus> tasks = new HashMap<>();
ApplicationState applicationState;
#StartSaga
#SagaEventHandler(associationProperty = "id")
public void on(ApplicationStateChangedEvent event) {
applicationState = event.getNewState();
log.info("Planning tasks for application {} in state {}", event.getId(), applicationState);
taskService.getTasksByState(applicationState).stream()
.map(task -> ScheduleTaskCommand.builder()
.applicationId(event.getId())
.taskId(IdentifierFactory.getInstance().generateIdentifier())
.targetState(applicationState)
.taskName(task.getTaskName())
.build())
.peek(command -> tasks.put(command.getTaskId(), SCHEDULED))
.forEach(command -> commandGateway.send(command));
}
#SagaEventHandler(associationProperty = "applicationId")
public void on(TaskFinishedEvent event) {
tasks.replace(event.getTaskId(), FINISHED);
long notFinished = getUnfinishedCount();
log.info("Task {} has just finished, ready {} of {}", event.getTaskName(), tasks.size() - notFinished, tasks.size());
if (notFinished == 0) {
log.info("All tasks for application {}.{} finished, ending this saga", event.getApplicationId(), applicationState);
eventBus.publish(GenericEventMessage.asEventMessage(
TaskForStateDoneEvent.builder()
.applicationId(event.getApplicationId())
.state(applicationState)
.build()
));
SagaLifecycle.end();
}
}
private long getUnfinishedCount() {
return tasks.values().stream()
.filter(state -> !FINISHED.equals(state))
.count();
}
}
And have this test (Spock) testing the first method:
class TasksForStateSagaTest extends Specification {
SagaTestFixture sagaFixture
def setup() {
sagaFixture = new SagaTestFixture<>(TasksForStateSaga)
}
def 'should schedule task for the application state'() {
given:
def applicationId = '1'
def taskService = Mock(TaskService)
def tasks = [
ApplicationStateAwareTaskDefinition.builder().taskName('task1').build(),
ApplicationStateAwareTaskDefinition.builder().taskName('task2').build(),
]
sagaFixture.registerResource(taskService)
sagaFixture.givenAggregate(applicationId)
when:
sagaFixture
.whenPublishingA(new ApplicationStateChangedEvent(id: applicationId, newState: ApplicationState.NEW))
.expectActiveSagas(1)
.expectDispatchedCommandsMatching(payloadsMatching(
exactSequenceOf(
equalTo(new ScheduleTaskCommand(applicationId: applicationId, targetState: ApplicationState.NEW, taskName: 'task1'),
new IgnoreField(ScheduleTaskCommand, 'taskId')),
equalTo(new ScheduleTaskCommand(applicationId: applicationId, targetState: ApplicationState.NEW, taskName: 'task2'),
new IgnoreField(ScheduleTaskCommand, 'taskId')),
andNoMore()
)
))
then:
1 * taskService.getTasksByState(ApplicationState.NEW) >> tasks
}
}
But I actually have no clue how to test the second method, which uses the Saga's internal state.
Could anyone advice me how to set the internal saga's state via SagaTestFixture?
Or even more, is this the good way how to implement such saga or I have there some conceptual issues preventing me to test the end saga method easily?
the #StartSaga method sets the internal state - generates taskId and sets it into map
the #EndSaga method reads the map and checks if all tasks are done prior it sends TaskForStateDoneEvent event
thanks!
I have achieved this by defining callback for the saga fixture. This callbacks catches all the commands of the desired type and stores the ids temporally
def 'saga should end when all scheduled tasks finished'() {
given:
def applicationId = '1'
def taskService = Mock(TaskService)
def tasks = [
ApplicationStateAwareTaskDefinition.builder().taskName('task1').build(),
ApplicationStateAwareTaskDefinition.builder().taskName('task2').build(),
]
def scheduleTaskCommands = []
sagaFixture.registerResource(taskService)
sagaFixture.setCallbackBehavior(new CallbackBehavior() {
#Override
Object handle(Object commandPayload, MetaData commandMetaData) {
if (commandPayload instanceof ScheduleTaskCommand) {
// catch the issued commands to get the new task ids
scheduleTaskCommands << commandPayload
}
commandPayload
}
})
when:
sagaFixture
.givenAggregate(applicationId)
.published(new ApplicationStateChangedEvent(
applicationId: applicationId,
newState: ApplicationState.NEW
))
// publish event to finish first task (first from two)
sagaFixture.givenAPublished(new TaskFinishedEvent(
taskName: 'someTaskName',
applicationId: applicationId,
taskId: scheduleTaskCommands[0].taskId,
taskResult: new TaskResult(
status: TaskResultStatus.SUCCEED,
message: 'ok'
)))
// verify the behaviour after second task finished
sagaFixture
.whenPublishingA(new TaskFinishedEvent(
taskName: 'someTaskName',
applicationId: applicationId,
taskId: scheduleTaskCommands[1].taskId,
taskResult: new TaskResult(
status: TaskResultStatus.SUCCEED,
message: 'ok'
)))
.expectActiveSagas(0)
.expectPublishedEvents(new TasksForStateFinishedEvent(
applicationId: applicationId,
state: ApplicationState.NEW
))
then:
1 * taskService.getTasksByState(ApplicationState.NEW) >> tasks
}
Related
I have a problem to understand a chained "RXJava-Retrofit" API call. I got inspired by this and implement this class named ObservationLoader to load the data from the API bucket per bucket. When the end of data is reached the API sends a endOfRecords=true:
public Observable<PageObject<Observation>> getAllObservationDataByRegion(long taxonKey,
String regionId) {
final PublishSubject<PageObject<Observation>> subject = PublishSubject.create();
return subject.doOnSubscribe(disposable -> {
this.getData(taxonKey, regionId, 0).subscribe(subject);
})
.doOnNext(observationPageObject -> {
if (observationPageObject.isEndOfRecords()) {
// -> list is completely loaded
subject.onComplete();
} else {
int nextOffset = observationPageObject.getOffset() + 1;
this.getData(taxonKey, regionId, null, nextOffset).subscribe(subject);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<PageObject<Observation>> getData(long id,
String regionId,
int offset) {
// Get your API response value
return this.api.getObservations(id, regionId, ObservationLoader.PAGE_LIMIT, offset);
}
In my Android fragment HomeFragment I subscribe to the ObservationLoader:
ObservationLoader loader = new ObservationLoader(this.getApi());
Observable<PageObject<Observation>> observable = loader
.getAllObservationDataByRegion(this.getSelectedSpecies(), this.getSelectedRegion());
observable.subscribe(new Observer<PageObject<Observation>>() {
#Override
public void onSubscribe(Disposable d) {
Log.i(TAG, "ON_SUBSCRIBE");
}
#Override
public void onNext(PageObject<Observation> observationPageObject) {
Log.i(TAG, "ON_NEXT");
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "ERROR = " + e.getMessage());
}
#Override
public void onComplete() {
Log.i(TAG, "COMPLETED");
}
});
I can see that the onSubscribe() and doOnSubscribe() are called and even the getData() is reached. I assume the API is responding correctly (a previous attempt attempt with recursion worked fine). But I never reached the doOnNext function. The observer goes straight to onComplete() and no data is received. What could be the reason?
When doOnSubscribe runs, the doesn't see any consumers yet so if getData is synchronous, there won't be any first results to trigger further results. Also if getData ends, it will complete the setup so the next getData call in doOnNext will push to an already terminated subject, ingoring all data.
You'll need a differently organized feedback loop:
// we loop back the nextOffset, in a thread-safe manner
Subject<Integer> subject = PublishSubject.<Integer>create()
.toSerialized();
// bootstrap with 0 and keep open for more offsets
subject.mergeWith(Observable.just(0))
// get the data for the current offset
.concatMap(nextOffset -> getData(taxonKey, regionId, nextOffset)
.subscribeOn(Schedulers.io())
)
// if the response is end of records, stop
.takeWhile(observationPageObject -> !observationPageObject.isEndOfRecords())
// otherwise not end of records, feedback the new offset
.doOnNext(observationPageObject ->
subject.onNext(observationPageObject.getOffset() + 1)
)
// get the data on the main thread
.observeOn(AndroidSchedulers.mainThread());
Issuing some cash within a Flow test - the flow returns the transaction with the output showing the correct Cash state. However, when I vault query for cash states, nothing is returned. Am I missing something?
IssueTokensFlow
#StartableByRPC
public class IssueTokensFlow extends FlowLogic<SignedTransaction> {
private static Double amount;
public IssueTokensFlow(double amount) {
this.amount = amount;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// Issue cash tokens equal to transfer amount
AbstractCashFlow.Result cashIssueResult = subFlow(new CashIssueFlow(
Currencies.DOLLARS(amount), OpaqueBytes.of(Byte.parseByte("1")), notary)
);
return cashIssueResult.getStx();
} }
IssueTokenFlow Test
#Test
public void testIssueCash() throws Exception {
IssueTokensFlow flow =
new IssueTokensFlow(100.00);
SignedTransaction transaction = a.startFlow(flow).get();
network.waitQuiescent();
Cash.State state = (Cash.State) transaction.getTx().getOutputStates().get(0);
assertEquals(state.getOwner(), chooseIdentity(a.getInfo()));
assertEquals(state.getAmount().getQuantity(), Currencies.DOLLARS(100.00).getQuantity());
// Above assertions pass
QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
Vault.Page<ContractState> results = a.getServices().getVaultService().queryBy(Cash.State.class, criteria);
assertTrue(results.getStates().size() > 0);
// ^ This assertion fails
}
In Corda 3, whenever you query a node’s database as part of a test (e.g. to extract information from the node’s vault), you must wrap the query in a database transaction, as follows:
node.transaction(tx -> {
// Perform query here.
}
So your test would become:
#Test
public void testIssueCash() throws Exception {
IssueTokensFlow2 flow = new IssueTokensFlow2(100.00);
SignedTransaction transaction = a.startFlow(flow).get();
network.waitQuiescent();
Cash.State state = (Cash.State) transaction.getTx().getOutputStates().get(0);
assertEquals(state.getOwner(), chooseIdentity(a.getInfo()));
assertEquals(state.getAmount().getQuantity(), Currencies.DOLLARS(100.00).getQuantity());
// Above assertions pass
QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
a.transaction(() -> {
Vault.Page<ContractState> results = a.getServices().getVaultService().queryBy(Cash.State.class, criteria);
assertTrue(results.getStates().size() > 0);
// ^ This assertion doesn't fail :)
return null;
});
}
I'm trying to invoke getStatus method for every 3 seconds and checking am I getting the Done status from my database (remove database code for this testing purpose). Once I got the status "Done" I'm coming out of while loop and I want to return this status to testMethod. But my code is not returning anything back to CompletableFuture. What I'm doing wrong here - can someone please help me to fix this? My code snippet:
CompletableFuture.supplyAsync({ -> getStatus()
}).thenAccept({ status -> testMethod(status) })
def getStatus() {
def response
Timer timer = new Timer()
TimerTask timerTask = new TimerTask(){
#Override
public void run() {
while(true) {
// Doing some DB operation to check the work status is changing to Done and assigning to response
response = "Done"
if (response == "Done") {
timer.cancel()
break;
}
}
}
}
timer.schedule(timerTask, 3000)
return response
}
def testMethod(status) {
System.out.println("testMethod... " +status)
}
The problem is you are scheduling the timer task and then immediately return the current value of response from getStatus(). 3 seconds later or so that task sets the local variable to "Done" but nobody outside the task is looking at that now.
A better approach might be for getStatus itself to return a CompletableFuture. Which the task can populate when it's done.
So something like this:
getStatus().thenAccept({ status -> testMethod(status) })
def getStatus() {
def future = new CompletableFuture<String>()
Timer timer = new Timer()
TimerTask timerTask = new TimerTask(){
#Override
public void run() {
while(true) {
// Doing some DB operation to check the work status is changing to Done and assigning to response
def response = "Done"
if (response == "Done") {
timer.cancel()
future.complete(response)
break;
}
}
}
}
timer.schedule(timerTask, 3000)
return future
}
def testMethod(status) {
System.out.println("testMethod... " +status)
}
EDIT - to add on some kind of timeout you might instead use a ScheduledExecutorService like so:
import java.util.concurrent.*
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2)
getStatus(executor).thenAccept({ status -> testMethod(status) })
def getStatus(executor) {
def future = new CompletableFuture<String>()
def count = 0
def task
def exec = {
println("Running : $count")
// Doing some DB operation to check the work status is changing to Done and assigning to response
def response = "NotDone"
if (response == "Done") {
future.complete(response)
task.cancel()
}
else if (++count == 10) {
future.complete("Failed")
task.cancel()
}
} as Runnable
task = executor.scheduleAtFixedRate(exec, 3, 3, TimeUnit.SECONDS)
future
}
def testMethod(status) {
System.out.println("testMethod... " +status)
}
So I just used an iteration count to stop it running more than 10 times but that could be count or time based. Whatever makes sense for your use case.
I need to know when the new state got committed into the node's vault for getting the timestamp at that moment. So, I think if I can handle the committed state then trigger my timestamp record class would be nice.
By the way, please let me know if you have any suggestions about capturing timestamp on state evolving over time.
Yes, you can do this using the CordaRPCOps.vaultTrackBy method. This method returns an observable of updates to the vault. You can subscribe to this observable to be notified whenever a new state is recorded in the vault.
RPC example
fun main(args: Array<String>) {
require(args.size == 1) { "Usage: ExampleClientRPC <node address>" }
val nodeAddress = NetworkHostAndPort.parse(args[0])
val client = CordaRPCClient(nodeAddress)
val rpcOps = client.start("user1", "test").proxy
val updates = rpcOps.vaultTrackBy<ContractState>().updates
// Log the 'placed' IOU states and listen for new ones.
updates.toBlocking().subscribe { update ->
update.produced.forEach { stateAndRef ->
val timeStamp = Instant.now()
// TODO("Use the timestamp as you wish.")
}
}
}
Flow test example
class FlowTests {
lateinit var network: MockNetwork
lateinit var a: StartedMockNode
lateinit var b: StartedMockNode
#Before
fun setup() {
network = MockNetwork(
listOf("com.example.contract"),
// We need each node to operate on a separate thread so that we can
// subscribe to the vault updates on a separate thread later.
threadPerNode = true)
a = network.createPartyNode()
b = network.createPartyNode()
listOf(a, b).forEach { it.registerInitiatedFlow(ExampleFlow.Acceptor::class.java) }
}
#After
fun tearDown() {
network.stopNodes()
}
#Test
fun `flowTest`() {
// Trying to access the node database in a separate thread would result in a
// `java.lang.IllegalStateException: Was expecting to find CordaPersistence set on current thread` exception
val updates = a.services.vaultService.trackBy<ContractState>().updates
Thread {
updates.toBlocking().subscribe { update ->
update.produced.forEach { stateAndRef ->
val timeStamp = Instant.now()
// TODO("Use the timestamp as you wish.")
}
}
}.start()
repeat(3) {
val flow = ExampleFlow.Initiator(1, b.info.singleIdentity())
a.startFlow(flow).getOrThrow()
}
// We give the other thread time to observe the updates.
Thread.sleep(10000)
}
}
How to use SignalR with Angular 2?
How to manually run change detection when receiving data from SignalR?
I recently wrote an article that demonstrates one way to integrate Angular 2 and SignalR using a "channel/event" model:
https://blog.sstorie.com/integrating-angular-2-and-signalr-part-2-of-2/
I don't think just linking to another site is considered appropriate, so here's the core of the Angular 2 service that exposes SignalR:
import {Injectable, Inject} from "angular2/core";
import Rx from "rxjs/Rx";
/**
* When SignalR runs it will add functions to the global $ variable
* that you use to create connections to the hub. However, in this
* class we won't want to depend on any global variables, so this
* class provides an abstraction away from using $ directly in here.
*/
export class SignalrWindow extends Window {
$: any;
}
export enum ConnectionState {
Connecting = 1,
Connected = 2,
Reconnecting = 3,
Disconnected = 4
}
export class ChannelConfig {
url: string;
hubName: string;
channel: string;
}
export class ChannelEvent {
Name: string;
ChannelName: string;
Timestamp: Date;
Data: any;
Json: string;
constructor() {
this.Timestamp = new Date();
}
}
class ChannelSubject {
channel: string;
subject: Rx.Subject<ChannelEvent>;
}
/**
* ChannelService is a wrapper around the functionality that SignalR
* provides to expose the ideas of channels and events. With this service
* you can subscribe to specific channels (or groups in signalr speak) and
* use observables to react to specific events sent out on those channels.
*/
#Injectable()
export class ChannelService {
/**
* starting$ is an observable available to know if the signalr
* connection is ready or not. On a successful connection this
* stream will emit a value.
*/
starting$: Rx.Observable<any>;
/**
* connectionState$ provides the current state of the underlying
* connection as an observable stream.
*/
connectionState$: Rx.Observable<ConnectionState>;
/**
* error$ provides a stream of any error messages that occur on the
* SignalR connection
*/
error$: Rx.Observable<string>;
// These are used to feed the public observables
//
private connectionStateSubject = new Rx.Subject<ConnectionState>();
private startingSubject = new Rx.Subject<any>();
private errorSubject = new Rx.Subject<any>();
// These are used to track the internal SignalR state
//
private hubConnection: any;
private hubProxy: any;
// An internal array to track what channel subscriptions exist
//
private subjects = new Array<ChannelSubject>();
constructor(
#Inject(SignalrWindow) private window: SignalrWindow,
#Inject("channel.config") private channelConfig: ChannelConfig
) {
if (this.window.$ === undefined || this.window.$.hubConnection === undefined) {
throw new Error("The variable '$' or the .hubConnection() function are not defined...please check the SignalR scripts have been loaded properly");
}
// Set up our observables
//
this.connectionState$ = this.connectionStateSubject.asObservable();
this.error$ = this.errorSubject.asObservable();
this.starting$ = this.startingSubject.asObservable();
this.hubConnection = this.window.$.hubConnection();
this.hubConnection.url = channelConfig.url;
this.hubProxy = this.hubConnection.createHubProxy(channelConfig.hubName);
// Define handlers for the connection state events
//
this.hubConnection.stateChanged((state: any) => {
let newState = ConnectionState.Connecting;
switch (state.newState) {
case this.window.$.signalR.connectionState.connecting:
newState = ConnectionState.Connecting;
break;
case this.window.$.signalR.connectionState.connected:
newState = ConnectionState.Connected;
break;
case this.window.$.signalR.connectionState.reconnecting:
newState = ConnectionState.Reconnecting;
break;
case this.window.$.signalR.connectionState.disconnected:
newState = ConnectionState.Disconnected;
break;
}
// Push the new state on our subject
//
this.connectionStateSubject.next(newState);
});
// Define handlers for any errors
//
this.hubConnection.error((error: any) => {
// Push the error on our subject
//
this.errorSubject.next(error);
});
this.hubProxy.on("onEvent", (channel: string, ev: ChannelEvent) => {
//console.log(`onEvent - ${channel} channel`, ev);
// This method acts like a broker for incoming messages. We
// check the interal array of subjects to see if one exists
// for the channel this came in on, and then emit the event
// on it. Otherwise we ignore the message.
//
let channelSub = this.subjects.find((x: ChannelSubject) => {
return x.channel === channel;
}) as ChannelSubject;
// If we found a subject then emit the event on it
//
if (channelSub !== undefined) {
return channelSub.subject.next(ev);
}
});
}
/**
* Start the SignalR connection. The starting$ stream will emit an
* event if the connection is established, otherwise it will emit an
* error.
*/
start(): void {
// Now we only want the connection started once, so we have a special
// starting$ observable that clients can subscribe to know know if
// if the startup sequence is done.
//
// If we just mapped the start() promise to an observable, then any time
// a client subscried to it the start sequence would be triggered
// again since it's a cold observable.
//
this.hubConnection.start()
.done(() => {
this.startingSubject.next();
})
.fail((error: any) => {
this.startingSubject.error(error);
});
}
/**
* Get an observable that will contain the data associated with a specific
* channel
* */
sub(channel: string): Rx.Observable<ChannelEvent> {
// Try to find an observable that we already created for the requested
// channel
//
let channelSub = this.subjects.find((x: ChannelSubject) => {
return x.channel === channel;
}) as ChannelSubject;
// If we already have one for this event, then just return it
//
if (channelSub !== undefined) {
console.log(`Found existing observable for ${channel} channel`)
return channelSub.subject.asObservable();
}
//
// If we're here then we don't already have the observable to provide the
// caller, so we need to call the server method to join the channel
// and then create an observable that the caller can use to received
// messages.
//
// Now we just create our internal object so we can track this subject
// in case someone else wants it too
//
channelSub = new ChannelSubject();
channelSub.channel = channel;
channelSub.subject = new Rx.Subject<ChannelEvent>();
this.subjects.push(channelSub);
// Now SignalR is asynchronous, so we need to ensure the connection is
// established before we call any server methods. So we'll subscribe to
// the starting$ stream since that won't emit a value until the connection
// is ready
//
this.starting$.subscribe(() => {
this.hubProxy.invoke("Subscribe", channel)
.done(() => {
console.log(`Successfully subscribed to ${channel} channel`);
})
.fail((error: any) => {
channelSub.subject.error(error);
});
},
(error: any) => {
channelSub.subject.error(error);
});
return channelSub.subject.asObservable();
}
// Not quite sure how to handle this (if at all) since there could be
// more than 1 caller subscribed to an observable we created
//
// unsubscribe(channel: string): Rx.Observable<any> {
// this.observables = this.observables.filter((x: ChannelObservable) => {
// return x.channel === channel;
// });
// }
/** publish provides a way for calles to emit events on any channel. In a
* production app the server would ensure that only authorized clients can
* actually emit the message, but here we're not concerned about that.
*/
publish(ev: ChannelEvent): void {
this.hubProxy.invoke("Publish", ev);
}
}
Then a component could use this service by subscribing (not in the rxjs sense...) to a specific channel, and reacting to specific events emitted:
import {Component, OnInit, Input} from "angular2/core";
import {Http, Response} from "angular2/http";
import Rx from "rxjs/Rx";
import {ChannelService, ChannelEvent} from "./services/channel.service";
class StatusEvent {
State: string;
PercentComplete: number;
}
#Component({
selector: 'task',
template: `
<div>
<h4>Task component bound to '{{eventName}}'</h4>
</div>
<div class="commands">
<textarea
class="console"
cols="50"
rows="15"
disabled
[value]="messages"></textarea>
<div class="commands__input">
<button (click)="callApi()">Call API</button>
</div>
</div>
`
})
export class TaskComponent implements OnInit {
#Input() eventName: string;
#Input() apiUrl: string;
messages = "";
private channel = "tasks";
constructor(
private http: Http,
private channelService: ChannelService
) {
}
ngOnInit() {
// Get an observable for events emitted on this channel
//
this.channelService.sub(this.channel).subscribe(
(x: ChannelEvent) => {
switch (x.Name) {
case this.eventName: { this.appendStatusUpdate(x); }
}
},
(error: any) => {
console.warn("Attempt to join channel failed!", error);
}
)
}
private appendStatusUpdate(ev: ChannelEvent): void {
// Just prepend this to the messages string shown in the textarea
//
let date = new Date();
switch (ev.Data.State) {
case "starting": {
this.messages = `${date.toLocaleTimeString()} : starting\n` + this.messages;
break;
}
case "complete": {
this.messages = `${date.toLocaleTimeString()} : complete\n` + this.messages;
break;
}
default: {
this.messages = `${date.toLocaleTimeString()} : ${ev.Data.State} : ${ev.Data.PercentComplete} % complete\n` + this.messages;
}
}
}
callApi() {
this.http.get(this.apiUrl)
.map((res: Response) => res.json())
.subscribe((message: string) => { console.log(message); });
}
}
I tried to map the SignalR concepts into observables, but I'm still learning how to effectively use RxJS. In any case I hope that helps show how this might work in the context of an Angular 2 app.
You can also try using ng2-signalr.
npm install ng2-signalr --save
takes care of ng2 change detection using zones
allows your server events to be listened to using rxjs.
Here is the link to the source.
You didn't specified wich syntax you're using to develop your Angular 2 app.
I will assume you're using typescript.
One approach is use Definitely Typed files.
1 - You'll need to download a Definitely Typed JQuery:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/jquery/jquery.d.ts
2 - After this, download a Definitely typed SignalR:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/signalr/signalr.d.ts
3 - Add the JQuery refence in your Component:
/// <reference path="../jquery.d.ts" />
4 - Now, you can call SignalR methods with intelissense. But you will need to use the Late Binding approach:
var connection = $.hubConnection();
var proxy = connection.createHubProxy(proxy.on("newOrder", (order) => console.log(order));
connection.start();
As far as examples go, there probably aren't any yet. Welcome to the beginning of a framework. But do keep checking over time because as popularity and adoption increases, there will sure to be many examples.
As far as running change detection, that's a very vague question as angular2's change detection is now very different, and much improved.
My approach is to just let angular2 handle it, and not trigger a manual change detection at all as most of the time Angular2 picks up on the change and re-renders the view.
If that does not work, then the next step is to trigger .run() on the NgZone
example:
import {NgZone, Component} from 'angular2/core';
#Component({...})
export class MyComponent{
myProperty: string = 'Hello';
constructor(myService: MyService, ngZone: NgZone){}
doSomething(){
this.myService.doSomething().then(x => {
this.ngZone.run(() => {
this.myProperty = x;
});
});
}
}
Again though, I have found that even working with asynchronous code, angular2 usually picks up on the change without using ngZone at all.