WhenAnyValue is not getting called by RaisePropertyChanged - xamarin.forms

I am new to ReactiveUI along with DynamicData
Declaration
ReadOnlyObservableCollection<Employee> itemSource;
public ReadOnlyObservableCollection<Employee> ItemSource
{
get => itemSource;
}
SourceList<Employee> Employees = new SourceList<Employee>();
Implementation
Employees
.Connect()
.Sort(SortExpressionComparer<Employee>.Ascending(emp => emp.ID))
.Bind(out itemSource)
.ObserveOn(RxApp.MainThreadScheduler)
.Do(_ =>
{
this.RaisePropertyChanged(nameof(ItemSource));
})
.DisposeMany()
.Subscribe()
.DisposeWith(disposable);
this.WhenAnyValue(x => x.itemSource)
.Do(_ =>
{
Debug.Print("Called");
});
But whenever I am calling
Employees.Add(new Employee
{
Name = "Hello" + DateTime.Now.ToShortDateString(),
ID = random.Next(1,1000)
});
I expect it to print "Called" in debug window as this.WhenAnyValue should be called, but apparently this is not the case.
Could you please help me to understand if I am doing any mistake?

Thanks Glen. I was able to fix it by using below code
this.WhenAnyValue(x => x.itemSource.Count)
.Do(_ =>
{
Debug.Print("Called");
});

Related

How do I combine multiple http requests and merge results

I need to handle a situation where I have 3 endpoints to call and would like to get the data in the most convenient/efficient way. The first call can be handled independently and returns a single result. The second endpoint returns a collection but will need to initiate 0-* subsequent calls, where a given key is present.
Ideally would like to receive the collection (from the 2nd endpoint call) as a mutated/new collection that includes the result from the 3rd endpoint call.
I am currently using forkJoin(observableA$, observableB$) to handle the first 2 calls in parallel but I cannot work out how to include the sequential calls and have the data included in observableB$
//Customer observable
const customer$ = this._customerManagementService.getCustomer(
accountNumber
);
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap((results: ISaleCycle[]) => {
return results.map(cycle => {
return this._purchaseVehicleService.getPurchaseVehicle(
cycle.vehicleKey
);
});
})
);
}
I expect the collection to include further data as a new property on the original collection
UPDATE
After a bit more thought maybe I should be using reduce somewhere in the solution. This way I can be in control of what's getting push into the array and it could be dynamic?
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
switchMap((results: ISaleCycle[]) => {
return results.map(cycle => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
reduce((acc, vehicle) => {
return { cycle: cycle, vehicle: vehicle };
}, []),
toArray()
);
}
else {
///No extra data to be had
}
});
}),
concatAll()
);
}
I would use concatMap() to merge the responses of HTTP requests 2 and 3.
import { of } from 'rxjs';
import { map, concatMap } from 'rxjs/operators';
const pretendGetCustomer = of({accountNumber: 123, name:"John Doe"});
const pretendGetVehiculeHttpRequest = (customerNumber) => {
return of([{custNum: 123, vehicleId:"2"}, {custNum: 123, vehicleId:"1"}]);
}
const pretendGetCyclesHttpRequest = (cycleIds) => {
return of([{id:"1", name:"yellow bike", retailPrice:"$10"}, {id:"2", name:"red bike", retailPrice:"$20"}]);
}
const yourFunction = () => {
pretendGetCustomer.subscribe(customer => {
// Assuming you do other things here with cust, reason why we are subscribing to this separately
// isHappy(customer)
// Your second & third calls
pretendGetVehiculeHttpRequest(customer.accountNumber).pipe(
// Need to use concatMap() to subscribe to new stream
// Note: use mergeMap() if you don't need the 1st stream to be completed
// before calling the rest
concatMap(purchases => {
const cyclesIds = purchases.map(p => p.vehicleId);
// concatMap() requires an Observable in return
return pretendGetCyclesHttpRequest(cyclesIds).pipe(
// Use map() here because we just need to use the data,
// don't need to subscribe to another stream
map(cycles=>{
// Retrun whatever object you need in your subscription
return {
customerNumber: customer.accountNumber,
customerName: customer.name,
purchases: purchases.map(p => cycles.find(c => p.vehicleId === c.id))
}
})
);
})
).subscribe(resultof2and3 => {
// Do something with the new/mutated Object which is a result of
// your HTTP calls #2 and #3
console.log(resultof2and3);
});
});
}
yourFunction();
I made a stackblitz if you want to see the above run (see console): https://stackblitz.com/edit/rxjs-nqi7f1
This is the solution I eventually came up with. I've taken the advice from BoDeX and used concatMap(). In my mind it was clear that I wanted to use forkJoin and be able to reference the results by object key, I.e customer or saleCycles.
In the scenario where a vehicleKey was present I needed to return the results in a defined data structure, using map(). Likewise, if no vehicle was found then I just needed the outer observable.
const customer$ = this._customerManagementService.getCustomer(accountNumber);
const saleCyclesWithVehicle$ = this.getSalesWithVehicle(accountNumber,dealerKey);
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap(cycles => {
return from(cycles).pipe(
concatMap((cycle: ISaleCycle) => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
map(vehicle => {
return { cycle: cycle, vehicle: vehicle };
})
);
} else {
return of({ cycle: cycle });
}
}),
toArray()
);
})
);
}
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));

Dispatch an action after another is executed in Effect

I Have the following function in my code
private loadDrones() {
this.store$.dispatch(new UsedDronesStoreActions.GetUsedDronesRequest({organizations_id : environment.organizationId}));
this.store$.pipe(select(UsedDronesStoreSelectors.selectAll)).subscribe((drones) => {
drones.forEach((drone) => {
if (this.drones.has(drone.id)) { return; }
this.drones.set(drone.id, drone);
this.store$.dispatch(new UsedDronesStoreActions.OpenUsedDroneUpdatePositionChannelRequest({ droneId: drone.id, projectId : environment.projectId }));
this.store$.dispatch(new UsedDronesStoreActions.OpenUsedDroneUpdateStatusChannelRequest({ droneId: drone.id, projectId : environment.projectId }));
});
});
}
I would like to move this function into an INIT Action in my effect.
Using
#Effect()
init$ = this.actions$.pipe(
ofType(ROOT_EFFECTS_INIT),
map(action => ...)
);
My question is, , the actual function load a list, and then thank to the result of that list, dispatch a serie of actions for each element of the list.
Is it possible to make this inside a single effect ? Or do I have to split it up in 2 different actions ?
https://medium.com/#amcdnl/dispatching-multiple-actions-from-ngrx-effects-c1447ceb6b22
#Effect() save = this.update$.pipe(
map(action => action.payload),
switchMap(payload => this.myService.save(payload)),
switchMap(res => [
new Notification('save success'),
new SaveSuccess(res)
])
);

Chain HTTP calls

I have a REST API that returns an array of appointments. For each item in this array I want to make an single call against another API to get more Information for the item.
this.httpClient.get(this.serverUrl, this.httpOptions.RequestOptions)
.map(res => res.json())
.map((items: Array<any>) => {
let list: Array<MettAppointmentModel> = [];
if (items) {
items.forEach(item => {
let model = new MettAppointmentModel();
model.Created = item.created;
model.CreatedBy = item.createdBy;
model.Date = item.date;
model.Id = item._id;
model.participated = this.httpClient.get(this.serverUrl + model.Id, this.httpOptions).map(response => return response.json());
list.push(model);
});
}
return list;
} );
I don't know how to get another call chained in this call
model.participated = this.httpClient.get(this.serverUrl + model.Id, this.httpOptions).map(response => return response.json());

Reference members in Moq lambda

I have a function in a class that returns a value based on the state of a class property. In this example, I want HasName() to return true if Name is not null. I could simply do Returns(false), however I want it evaluate as a lambda so that it works properly if Name is modified during the test.
public interface IThing
{
string Name { get; set; }
bool HasName();
}
var mocks = new Dictionary<string, IThing>();
Mock<IThing> mockThing;
mockThing = new Mock<IThing>();
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
mocks["first"] = mockThing.Object;
mockThing = new Mock<IThing>();
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
mocks["second"] = mockThing.Object;
Console.WriteLine(mocks["first"].HasName());
mocks["first"].Name = null;
Console.WriteLine(mocks["first"].HasName());
The 2nd Console.WriteLine prints true instead of false due to scoping (referencing the 2nd mock). Resharper actually complains of "Access to modified closure". What is the correct way to do this?
Although your design a little bit strange but you can access the generated mock object with mockThing.Object in your Setup function:
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
var thing = mockThing.Object;
var hasName = thing.HasName(); // true because Name returns "test"
thing.Name = null;
hasName = thing.HasName(); // false
The problem is that you are referencing the mockThing with your lambdas and then you are reasigning it. So both setup will end up using the same instance.
Use the mocks from the dictionary and it will work:
var mocks = new Dictionary<string, IThing>();
Mock<IThing> mockThing;
mockThing = new Mock<IThing>();
mocks["first"] = mockThing.Object;
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mocks["first"].Name != null;
});
mockThing = new Mock<IThing>();
mocks["second"] = mockThing.Object;
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mocks["second"].Name != null;
});
Console.WriteLine(mocks["first"].HasName());
mocks["first"].Name = null;
Console.WriteLine(mocks["first"].HasName());

How to display enum description or name in a grid row?

I am using the Kendo grid in my ASP.Net MVC application. If I have the following grid definition,
#(Html.Kendo().Grid(Model) //Bind the grid to ViewBag.Products
.Name("grid")
.Columns(columns =>
{
columns.Bound(p => p.FullName);
columns.Bound(p => p.MyEnum)
})
.Groupable()
.Pageable()
.Sortable()
.Scrollable(scr => scr.Height(600))
.Filterable()
)
where one of the column is an Enum. My enum definition is:
public enum MyEnum
{
[Display(AutoGenerateField = false, Name = "My enum 1", Description = "My Enum 1")]
EnumOne,
[Display(Name = "My Enum 2")]
EnumTwo
}
How do I make it display "My Enum 1" or "My Enum 2" for each row?
Thanks in advance!
I recently ran into this problem and solved it by using
var someArrayOfValueAndText = new[] {new {
Id = 0, Description = "Foo"
}, new {
Id = 1, Description = "Bar"
}
.Columns(c.ForeignKey(m=> m.MyEnum, someArrayOfValueAndText, "Id","Description"))
instead of the .Bound method
I created an helper class containing some extension methods a while back:
public static class EnumExtensions
{
public static string GetDisplayName(this Enum enu)
{
var attr = GetDisplayAttribute(enu);
return attr != null ? attr.Name : enu.ToString();
}
public static string GetDescription(this Enum enu)
{
var attr = GetDisplayAttribute(enu);
return attr != null ? attr.Description : enu.ToString();
}
private static DisplayAttribute GetDisplayAttribute(object value)
{
Type type = value.GetType();
if (!type.IsEnum)
{
throw new ArgumentException(string.Format("Type {0} is not an enum", type));
}
// Get the enum field.
var field = type.GetField(value.ToString());
return field == null ? null : field.GetCustomAttribute<DisplayAttribute>();
}
}
It contains two methods for extracting the Name and Description of a Display attribute. The display name:
columns.Bound(p => p.MyEnum.GetDisplayName())
And for a description:
columns.Bound(p => p.MyEnum.GetDescription())
You have to add a using statement in your Web.config or in your view.
Update
What if you create a property for it in your model:
public string MyEnumName
{
get { return MyEnum.GetDisplayName(); }
}
Now you should be able to use:
columns.Bound(p => p.MyEnumName);
Henk's solution is good. But you can use filtering capability if you use ClientTemplate:
col.Bound(m => m.MyEnum) // this provides you filtering
.ClientTemplate("#: MyEnumName #") // this shows a name of enum
For more information about kendo ui templates see: http://docs.telerik.com/kendo-ui/framework/templates/overview
I use #user1967246 method and would like to explain more how to i do.
i created array in top of my kendo grid
var statusLikeEntityStatus = new[]
{
new {Id = 0, Status = EntityStatus.Passive},
new {Id = 1, Status = EntityStatus.Active},
new {Id = 2, Status = EntityStatus.Draft},
new {Id = 3, Status = EntityStatus.ReadyToLive},
new {Id = -1, Status = EntityStatus.Freezed},
new {Id = -2, Status = EntityStatus.Blocked}
};
Then i use ForeignKey property instead of Bound.
columns.ForeignKey(m => m.Status, statusLikeEntityStatus, "Id", "Status").Title(Resources.General.Status);
Here is my columns attribute
.Columns(columns =>
{
columns.Bound(m => m.InventoryID).Title("Id");
columns.Bound(m => m.ERPCode).Title(Resources.Products.ProductCode);
columns.Bound(m => m.Price).Title(Resources.Products.ListPrice);
columns.Bound(m => m.Discount).Title(Resources.Products.
columns.Bound(m => m.Stock).Title(Resources.Products.TotalStock); // todo: Resources
columns.ForeignKey(m => m.Status, statusLikeEntityStatus, "Id", "Status").Title(Resources.General.Status);
columns.Command(commandConf =>
{
commandConf.Edit();
commandConf.Destroy();
});
})
Hope it will help to you.

Resources