ObservableChangeSet wait until list is ready before watching - dynamic-data

We have a list page where we can enable or disable a thing™ using a <switch /> That thing™ is toggled with an IsActive flag
public class Thing
{
/* ... */
[Reactive] public bool IsActive { get; set; }
}
Given the following change listener, the idea is when the IsActive property changes (user interaction on a toggle switch), we invoke the _saveItemCommand to save the entity with the new IsActiveState.
public ObservableCollection<Thing> DataObjectList {get;} = new ObservableCollection<Thing>();
public MyClass()
{
_saveItemCommand = ReactiveCommand.CreateFromTask(SaveItemInternal);
_listWatcher = DataObjectList
.ToObservableChangeSet()
.AsObservableList()
.Connect()
.WhenPropertyChanged(x => x.IsActive)
.Throttle(TimeSpan.FromMilliseconds(250))
.ObserveOn(RxApp.MainThreadScheduler)
.Select(x => x.Sender)
.InvokeCommand(_saveItemCommand);
}
public void OnNavigatingTo()
{
var newItems = _myService.GetNewItems();
DataObjectList.AddRange(newItems);
}
public void OnDestroy()
{
_listWatcher?.Dispose();
}
The problem I'm having is that when I setup the list, The command seems to be invoked on the last item in the list immediately after AddRange is called.
I have tried using .Skip(1) without any luck, but one thing that seems to work but is ugly is .Skip(DataObjectList.Length)
How can I make it so that the command isn't invoked until the first time the user toggles the switch? What is the correct way to setup this listener?

Most likely you'll want to add a Where statement to indicate it should only be called on the IsActivated switch.
_listWatcher = DataObjectList
.ToObservableChangeSet()
.AsObservableList()
.Connect()
.WhenPropertyChanged(x => x.IsActive)
.Throttle(TimeSpan.FromMilliseconds(250))
.ToCollection()
.Where(x => x.Any(value => value.IsActive))
.ObserveOn(RxApp.MainThreadScheduler)
.Select(x => x.Sender)
.InvokeCommand(_saveItemCommand);
So the two lines I added are
.ToCollection()
.Where(x => x.Any(value => value.IsActive))
The ToCollection() will convert it into an observable list and the Where will restrict your observable to when there is change of the IsActive values.
You may wish to add a FirstAsync() call if you want it to happen only once after the Where() call.

After the comments on Glenn's answer and some additional conversations with Rodney, here's what finally works.
_listWatcher = DataObjectList
.ToObservableChangeSet()
.AsObservableList()
.Connect()
.WhenPropertyChanged(x => x.IsActive)
.Throttle(TimeSpan.FromMilliseconds(250))
.Skip(1)
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Select(x => x.Sender)
.InvokeCommand(_createActivationsInternal);

Related

How can I display A ViewBag using information from my database and using .include()?

I am new to ASP.NET/EntityFramework and I have created a simple Bank app. The problem I am having is that I can't get the balance to display.
here is the key part that I need to use in my model:
public double Balance
{
get { return Transactions.Sum(transaction => transaction.Amount); }
}
and here is the key part in my controller that I need to use:
[HttpPost("/money")]
public IActionResult Money(Transaction trans)
{
if(ModelState.IsValid)
{
ViewBag.balance = dbContext.Transactions
.Include(t => t.Amount);
dbContext.Transactions.Add(trans);
dbContext.SaveChanges();
return RedirectToAction("Index");
}
var user = loggedInUser;
ViewBag.User = user;
ViewBag.Transactions = dbContext.Transactions
.OrderByDescending(t => t.CreatedAt)
.Where(t => t.UserId == loggedInUser.UserId);
return View("Index");
}
and here is my View where I am trying to get the balance using ViewBag:
<p>Current balance: #ViewBag.User.Balance</p>
Im assuming the problem has something to do with this:
ViewBag.balance = dbContext.Transactions
.Include(t => t.Amount);
First, make sure you can get value from dbContext.Transactions.Include(t => t.Amount);.
Second, you set ViewBag.balance = ..., so in your view. You can get it by <p>Current balance: #ViewBag.balance</p>.
Tips:
If you want get value from User model, you need add #model User, then you can use something like #Model.Balance to get balance.
For more details, you can refer official doc:
Part 4, add a model to an ASP.NET Core MVC app

How to pass a RenderFragement<object> Template to a Blazor component

I'm stucking on one special thing where I try to pass an object of RenderFragment to a dynamically generated component.
I considered this sample from Devexpress https://docs.devexpress.com/Blazor/401753/common-concepts/customize-and-reuse-components
<DxFormLayout>
<DxFormLayoutTabPages>
#renderLayoutTabPage()
</DxFormLayoutTabPages>
</DxFormLayout>
#code {
private RenderFragment renderLayoutTabPage() {
RenderFragment item = b => {
b.OpenComponent<DxFormLayoutTabPage>(0);
b.AddAttribute(1, "Caption", "My tab");
b.AddAttribute(2, "ChildContent", (RenderFragment)((tabPageBuilder) => {
tabPageBuilder.OpenComponent<DxFormLayoutItem>(0);
tabPageBuilder.AddAttribute(1, "Caption", "DynLayoutItem");
tabPageBuilder.AddAttribute(2, "ColSpanMd", 6);
tabPageBuilder.AddAttribute(5, "Template", (RenderFragment<Object>)((context) => ((itemTemplate) => {
itemTemplate.OpenComponent<DxTextBox>(0);
itemTemplate.AddAttribute(1, "Text", text);
itemTemplate.CloseComponent();
})));
tabPageBuilder.CloseComponent();
}));
b.CloseComponent();
};
return item;
}
}
So this is the way they are building a complete new ChildContent Fragment.
my razor file looks like:
[Parameter] RenderFragment<object> DisplayTemplate {get;set;} //pass this Fragement to the dynamic component
protected override void OnInitialized()
{
...
b.AddAttribute(3, nameof(DxDataGridColumn.DisplayTemplate), (RenderFragment<Object>)((context) => ((itemTemplate) =>
{
itemTemplate.AddContent<object>(0, DisplayTemplate, context);
})));
...
}
When I run this, the DisplayTemplate does not get rendered. I only can see the type string "Microsoft.AspNetCore.Components.RenderFragment`1[System.Object]"
What I'm doing wrong here?
Got it!
In my Component I was using this pattern
<MyComponent>
<DisplayTemplate>
#DisplayTemplate
</DisplayTemplate>
<MyComponent>
The only way it works correct is to use the Attributes directly:
<MyComponent DisplayTemplate"#DisplayTemplate" />

Use Mapper.Initialize() for multiple mappings

I use AutoMapper V.6.1.1 as a mapper in my ASP.Net project.
Before I had configuration as below:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<A, B>();
cfg.CreateMap<C, D>().ForMember(dest => dest.CityDesc, opt => opt.MapFrom(src => src.City));
});
var mapper = config.CreateMapper();
var var1= mapper.Map<B>(request);
var var2= mapper.Map<List<C>, List<D>>(result);
Now, I want to refactor the code, using Mapper.Initialize(). So I used:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<A, B>();
cfg.CreateMap<C, D>().ForMember(dest => dest.CityDesc, opt => opt.MapFrom(src => src.City));
});
var var1= Mapper.Map<B>(request);
var var2= Mapper.Map<List<C>, List<D>>(result);
I have an run time error:
Missing type map configuration or unsupported mapping. Mapping types: A-> B
Is there any problem with using multiple configurations in Mapper.Initialize? There is no error in the case that has one mapping in Initialize() body. How should I fix the error?
Maybe you have more than one Mapper.Initialize in your project while you should not have multiple Mapper.Initialize in your project else it will become override and you lost previous mapping configurations that you set by Mapper.Initialize. Now It is possible to get the error (Missing type map configuration or unsupported mapping.)
I recommend you to use AutoMapper.Profile. You can warp your mapping configurations in the form of grouped (in separated Profiles) then register all of theme by Mapper.Initialize at once ;)
Look at this example:
public class AB_Profile : Profile {
protected override void Configure() {
CreateMap<A, B>();
// CreateMap<A, B1>();
// CreateMap<A, B2>();
}
}
public class CD_Profile : Profile {
protected override void Configure() {
CreateMap<C, D>()
.ForMember(dest => dest.CityDesc, opt => opt.MapFrom(src => src.City));
}
}
Then initialize the Mapper using above Profiles:
Mapper.Initialize(cfg => {
cfg.AddProfile<AB_Profile >();
cfg.AddProfile<CD_Profile >();
});
Starting version 5 use this, as mentioned on their website...
public class OrganizationProfile : Profile
{
public OrganizationProfile()
{
CreateMap<Foo, FooDto>();
// Use CreateMap... Etc.. here (Profile methods are the same as configuration methods)
}
}
// How it was done in 4.x - as of 5.0 this is obsolete:
// public class OrganizationProfile : Profile
// {
// protected override void Configure()
// {
// CreateMap<Foo, FooDto>();
// }
// }
See Doc
Then initialize the mapping as...
Mapper.Initialize(cfg => {
cfg.CreateMap<Foo, Bar>();
cfg.AddProfile<OrganizationProfile>();
});

HTTP: getById creates new instance, which is missing an Array (Angular 2 RC1 + TS)

I got some really good help to a previous question at: "TypeError.parent.context.car.getBrands is not a function": s
and that is related to my current question. As can seen in that answer to my previous error, my app won't work, unless I create an new instance of "car", but hen I call that method:
getById(id: string) {
return this.http.get('app/car.json'+id)
/*
if I log the incoming data here to the console,
the correct data from server comes, eg: 'id: id, name: name, brands: Array[2]'
*/
.map(data => data.json())
.map(car => new Car(car.id, car.name)); //<== this line causes problem!
}
receiving component:
routerOnActivate(curr: RouteSegment): void {
let id = curr.getParam('id');
this._service.getById(id)
.subscribe(car => {
// this code is executed when the response from the server arrives
this.car = car;
console.log("res: ", this.car);// <=== correct car, without the array of brands
});
// code here is executed before code from the server arrives
// event though it is written below
}
it creates a new instance "Car". Well that is all good, but the Car also contains an Array of Brands.
My service looks like this:
#Injectable()
export class Service {
constructor(private http: Http) { }
getCars(){
return this.http.get...
}
getById(id: string) {
return this.http.get...
}
}
and my Car class like:
export class Car {
private brands: Array<Brand>;
constructor(public id: string, public name: string) {
this.brands = new Array<Brand>();
}
public getBrands(): Array<Brand> {
return this.brands;
}
//some other methods.
}
So I also have some data in the brands Array, but since the getById method creates a new car, it only takes the parameter id and name, and the brands array becomes empty! I don't know how to get the data from the server side so that it includes the array of brands!
I've (desperately) tried creating an Car in my service, which does log the correct data... but obviously doesn't work.
getById(id: string) {
this.http.get('app/car.json'+id)
.map((res: Response) => res.json())
.subscribe(car => {
//this code is executed when the response from the server arrives
this.car = car;
console.log("res: ", this.car); // <==== correct data!
return this.car;
});
//return this.car placed here doesn't give void error, but returns an undefined car, since the code gets executed before subscribe!
}
and receiving component:
routerOnActivate(curr: RouteSegment){
let id = curr.getParam('id');
this.car = this._service.getById(id); //error: Type 'void' is not assignable to type 'Car'
}
Any advice to give? Thanks!
It's been ages, but I thought I would post the solution to my problem. I had to create a static method to get the app to work. As follows:
getById(id:string)
return this.http.get('app/car.json'+id)
.map(data => data.json())
.map(data => Car.carFromJSON(data))
Then in my Car class:
static carFromJSON(json) {
let id = json.id
let name = json.name
let brands: Brand[] =
json.brands.map (brand => new Brand())
return new Car(id, name, brands)
}
You're initializing your car with empty array every time:
export class Car {
private brands: Array<Brand>;
constructor(public id: string, public name: string) {
this.brands = new Array<Brand>(); <-- every new Car() will end up with empty array
}
public getBrands(): Array<Brand> {
return this.brands;
}
//some other methods.
}
You have to extend your constructor with brands:
constructor(public id: string, public name: string, public brands: Brand[]) {}
And then call:
getById(id: string) {
return this.http.get('app/car.json'+id)
.map(data => data.json())
.map(car => new Car(car.id, car.name, car.brands)); // <-- add brands
}

GraphDiff: "Cascading" Owned Collections

I'm trying to use GraphDiff (latest available version in NuGet) to handle what I consider a not terribly difficult entity model. Consider a model like so:
class A
{
public virtual ICollection<B> Bs { get; set; }
}
class B
{
public virtual ICollection<C> Cs { get; set; }
}
If I'm updating an instance of A (call it aEntity), I could do:
context.UpdateGraph(aEntity, map =>
map.OwnedCollection(a => a.Bs, withB =>
withB.OwnedCollection(b => b.Cs)))
Now I'd also, sometimes, like to update B's independently.
context.UpdateGraph(bEntity, map => map.OwnedCollection(b => b.Cs));
So I figured I could "cascade" the changes by introducing a property, like so:
class BMapper {
Expression<Func<IUpdateConfiguration<B>, object>> MappingExpression
{
get
{
return map => map.OwnedCollection(b => b.Cs);
}
}
}
... and then use that in both scenarios like so:
// Update an A and any of its B's
context.UpdateGraph(aEntity, map =>
map.OwnedCollection(a => a.Bs, (new BMapper()).MappingExpression))
// Update a B by itself
context.UpdateGraph(bEntity, (new BMapper()).MappingExpression);
Updating the B by itself works fine, but updating an A falls down in expression land:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
'string' does not contain a definition for 'Body'
Is there a way to share mappings in GraphDiff?

Resources