I have a service with an http Post
saveItem(item: Item): Observable<number> {
return this.http
.post(`${this.baseUrl}/items`, item.createJson(), this.getHeaders())
.map(this.getIdFromItem)
.catch(this.handleError);
}
I call this in a save method in my component
save(item: Item): boolean {
if (!this.isValid()) {
return false;
}
this.itemService.saveItem(this.item)
.subscribe(id => {
this.item.id = id;
return true;
},
error => {return false;}
)
}
Within the same component, the user can upload an image related to the item - but I need to save the item before they upload the image.
{
if (!this.save(this.item)) {
alert('You cannot upload an image before saving the item');
return false;
}
The problem is that this.save() doesn't return after the save is complete, but immediately - so my image upload continues (and then fails on the server as it has no Id to associate with it).
I can move the 'isValid' to check before saving - but if the save fails on the server, it's too late.
Do I need to change my save to return an observable and then subscribe to it from my file upload? (if so could you show me some code how to do that?) or do I need to do something to get the value from the Observable before returning from the 'save' method (if so could you show me some code how to do that?)
I know there are other ways of solving this (upload the image anyway and associate it when the item is eventually saved, for example - but I don't like that idea - and I'd like an answer on this as much out of interest as solving this real problem.)
Thanks
the getIdFromItem gets the inserted item's Id from the JSON response - thought it worth mentioning.
You can use a flag that informs the rest of your code if you can upload or not.
service:
saveItem(item: Item): Observable<number> {
return this.http
.post(`${this.baseUrl}/items`, item.createJson(), this.getHeaders())
.map(this.getIdFromItem)
.catch(this.handleError);
}
component:
canUpload: boolean = false;
save(item: Item): void {
this.canUpload = false;
this.itemService.saveItem(this.item)
.subscribe(id => {
this.item.id = id;
this.canUpload = true;
},
error => {return false;}
)
}
upload(): boolean {
if (!this.canUpload) {
console.log('You cannot upload an image before saving the item');
return false;
}
this.canUpload = false;
console.log('You can upload now');
// ...
}
Related
Following is the code flow for the particular scenario. First, a POST call. This POST call updates a table's column in database. Then, subscribe to it, and make a GET call. Using the result of this GET call, I update a parameter on the page. After both these calls are done, I should make an PUT call to update some variables(which not getting retrieved from the previous GET call), and then emit an event. Below is the code:
save({ job, status }) {
this.apiService.serviceMethod(job, status);
console.log('calls after publishTaskAction');
this.apiService.updatePutMethod(job).subscribe(() => {
console.log('after updateTask');
});
this.notify.emit('saved!');
}
ApiService methods:
serviceMethod(job: Model, status: string) {
if (status) {
let observ = of() as Observable<void>;
if (status === 'Publish') {
observ = this.firstPostMethod(task);
}
if (status === 'UnPublish') {
observ = this.firstPostMethod(task);
}
console.log('calls before observ');
observ.subscribe(() => {
console.log('inside observ');
this.firstGETCall(task).subscribe();
console.log('after observ');
});
}
}
What I am trying to achieve is that serviceMethod should complete the execution(complete both the Post and Get call), and then go back to the caller method and execute updatePutMethod. What is happening is that the execution is returning to the caller and updatePutMethod is executed and then observ.subscribe is called. Due to this, at backend the result is not as expected. This the order in which console.logs are executed:
calls before observ
calls after publishTaskAction
before updateTask http call
inside observ
after observ
tafter updateTask
I followed this solution: subscribe getting called after below codes execution?. and updated my code as below:
save({ job, status }) {
this.apiService. serviceMethod(job, status).subscribe(
(data) => {
this.apiService.firstPostMethod(job).subscribe(
(data) => {
this.taskApiService.updateTask(task).subscribe();
}
);
this.notify.emit('saved!');
});
}
But this is wrong. Also, I read from few sources that a subscribe shouldn't be called from another subscribe block. So please help with any suggestion.
This is where pipe for RxJS is handy. You can continue one observable after another before calling the final subscribe.
To reiterate your desired functionality:
Within your UI component you press the Save button which takes a job and status. You want to POST the job to save a task to the database, then do a GET to retrieve additional data.
Note, before continuing on, if you are POSTing to save TASK and the following GET is to retrieve the updated TASK, you can always return the updated TASK from the POST call.
Once you get the updated data, you update UI which then updates an additional variable that you then need to update using the PUT call.
Finally, once all variables have been POSTed, GETted, PUTted and the UI updated, you want to emit to the parent component all the operations are complete.
My recommendation.
save(job: Model, status: string) {
console.log('Starting apiService.serviceMethod');
this.apiService.serviceMethod(job, status).pipe(
switchMap((job: Model) => {
if (job) {
// Some update of job is require here since the GET call doesn't know
// whereas the UI does.
// Modify job variable here.
console.log('Starting PUT call');
return this.apiService.updatePutMethod(job);
} else {
console.log('Skipping PUT call');
return of(null);
}
})
).subscribe(() => {
console.log("Ready to emit");
this.notify.emit('Saved!');
});
}
import { of } from 'rxjs';
serviceMethod(job: Model, status: string): Observable<Model> {
if (status && (status === 'Publish' || status === 'UnPublish')) {
console.log('Staring POST call');
return this.firstPostMethod(task).pipe(
switchMap((task) => {
console.log('Starting GET call');
return this.firstGETCall(task);
})
);
} else {
// either status is null/empty or does not equal either of the strings.
return of(null);
}
}
I am trying to see what would be the best way to approach this. I am using MVC .Net Core Web App.
When a user clicks a "Create Ticket" button, it checks to see how many tickets are open. If more than 5 tickets are open, then display toast alert message. If not, then create ticket.
public IActionResult Create()
{
var ticket = _context.tickets
.where(x => x.statusID == "1") //1 = open
if(ticket.Count() > 5){
//from my research many people use tempData here
TempData["Alert"] = "You have exceeded limit"
return ? //What do I return???
}
Ticket ticket = new Ticket();
_context.ticket.Add(ticket);
_context.ticket.SaveChanges();
return RedirectToAction("Index");
}
I want to display the alert without refreshing the page. Will the best approach be to do an Ajax call when button is clicked? If so, would it look like this
$(function () {
$("#Createbtn").click(function (e) {
e.preventDefault();
$.ajax({
cache: false,
type: "GET",
url: "/Tickets/CreateValidation",
datatype: "json",
success: function (data) {
alert(data);
},
})
})
})
Then from the action I can redirect to "Create" action?
I appreciate the input. Thanks.
As far as I know, if you want to return message to the client ajax success function, you should return OKresult in your controller method.
Besides, since you use ajax to call the method function, it will return the index html makeup instead of redirect to the index method when you call RedirectToAction.
In my opinion, you should check the message in the client success function. If the return message is a url, you should use window.location.href to redirect to the returned url.
Details, you could refer to below codes:
[Route("Tickets/CreateValidation")]
public IActionResult Create()
{
// var ticket = _context.tickets
// .where(x => x.statusID == "1") //1 = open
//if (ticket.Count() > 5)
// {
// //from my research many people use tempData here
// TempData["Alert"] = "You have exceeded limit"
// return ? //What do I return???
// }
// Ticket ticket = new Ticket();
// _context.ticket.Add(ticket);
// _context.ticket.SaveChanges();
// return RedirectToAction("Index");
//I don't have your dbcontext, so I create a ticket number directly
var ticketnumber = 4;
if (ticketnumber >5)
{
//If we want to return string to the client side inside a post action method, we could directly using OK or Json as the result.
//If we use OK to return data, the we could receive this data inside the ajax success method
return Ok("You have exceeded limit");
}
return Ok("/Default/Index");
}
Client-side ajax:
$(function () {
$("#Createbtn").click(function (e) {
e.preventDefault();
$.ajax({
cache: false,
type: "GET",
url: "/Tickets/CreateValidation",
datatype: "json",
success: function (data) {
if (data == "/Default/Index") {
window.location.href = data;
} else {
alert(data);
}
}
})
})
})
</script>
Result:
If the ticket number > 5:
If the ticket number <5:
You could find the page has refreshed.
I have list of users for chat purpose, something like on facebook where i got all users from my database using ngResource. When user is offline i got red marker close to his name and when is online i use green marker.
What i want to archieve is that when user sign in, my red marker will turn into green. When user login into my app, my Hub method OnConnected() gets fired and call my client side code
Hub method when user sign in.
#region Connect
public override Task OnConnected()
{
var userDetails = new ApplicationUser
{
ConnectionId = Context.ConnectionId,
UserName = Context.Request.GetHttpContext().User.Identity.Name,
Id = HttpContext.Current.User.Identity.GetUserId(),
};
if (onlineUsers.Count(x => x.ConnectionId == userDetails.ConnectionId) == 0)
{
onlineUsers.Add(new ApplicationUser {
ConnectionId = Context.ConnectionId,
UserName = userDetails.UserName,
Id = userDetails.Id,
});
Clients.All.newOnlineUser(userDetails);
Clients.Caller.getOnlineUsers(onlineUsers);
}
return base.OnConnected();
}
#endregion
Client side code in my controller
$scope.online_users = UserService.getChatUsers();
PrivateChatService.addOnlineUser(function (user) {
angular.forEach($scope.online_users, function (value, key) {
if (user.UserId == value.Id) {
value.Active = true;
}
});
console.log("newOnlineUser finished");
});
Problem is with forEach method in my client side code. In that time when my signalR hub fires my method ".addOnlineUser" my $scope.online_users is not resolved so i only have promise but not data so i cant iterate through that array to change user status from offline to online. Is something how i can wait for promise to be resolved?
Update:
I had something like this but this is not definitely good aproach since i hit all the time my database to get users.`
PrivateChatService.addOnlineUser(function (user) {
var dataPromise = UserService.getChatUsers(function(response){
$scope.online_users = response;
angular.forEach(dataPromise, function (value, key) {
if (user.UserId == value.Id) {
value.Active = true;
}
});
});
console.log("newOnlineUser finished");
});
in my Nativescript Angular app i am using an ActivityIndicator, setup as i've seen in the Nativescript Angular docs (the GroceryList example):
<ActivityIndicator width="30" height="30" [busy]="refreshing" [visibility]="refreshing ? 'visible' : 'collapsed'" horizontalAlignment="center" verticalAlignment="center"></ActivityIndicator>
if the Component using it i have:
export class MyComponent {
public refreshing = false;
........
}
Then i fetch some data from my backend:
public onRefreshTap() {
console.log("onrefreshtap");
this.refreshing = true;
this.backend.getData(function (data) { //this.backend is my Service
this.refreshing = false;
})
}
The problem is that when i put this.refreshing to true, the ActivityIndicator correctly shows. But when bakend request completes (and so, i put this.refreshing=false) the ActivityIndicator does not hides... (and also it seems that its busy property is not updated, it stays in spinning state)..
What am i doing wrong ?
Thanks in advance
You could also try to access the refreshing property as it has been shown in the sample codes below. It could be a problem of accessing the property inside the callback method of your service.
public onRefreshTap() {
var that = this;
this.refreshing = true;
this.backend.getData(function (data) { //this.backend is my Service
that.refreshing = false;
})
}
or
public onRefreshTap() {
this.refreshing = true;
this.backend.getData((data) => {
that.refreshing = false;
})
}
It may be many things:
1) The change to false, on the Observable, is not being "seen" by the component.
------ The solution is run the code in a Zone (see https://angular.io/docs/ts/latest/api/core/index/NgZone-class.html )
2) The backend is returning an error (I don't see it dealing with that in the code).
------ The solution is put a function to deal with the error.
3) The callback is not being called. In your code, you're SENDING a function as a parameter to the backendService, so maybe the service is not executing it.
------ Try using a Promisses or Observables to deal with returned values (you'll have to Google about it, since I'm still learning them my explanation would be the worst). :)
Here's some code that might work:
my-component.html
<ActivityIndicator [busy]="isWorking" [visibility]="isWorking?'visible':'collapse'"></ActivityIndicator>
my-component.ts
import { Component, NgZone } from "#angular/core";
...
export class MyComponent {
isWorking:boolean = false;
constructor(private backendService: BackendService,
private _ngZone: NgZone)
{
this.isWorking = false;
}
public onRefreshTap() {
console.log("onrefreshtap");
this.isWorking = true;
this.backendService.getData()
.then(
// data is what your BackendService returned after some seconds
(data) => {
this._ngZone.run(
() => {
this.isWorking = false;
// I use to return null when some Server Error occured, but there are smarter ways to deal with that
if (!data || data == null || typeof(data)!=='undefined') return;
// here you deal with your data
}
)
}
);
}
}
In a WPF app that follows the MVVM pattern, I've run across a common issue where a user clicks on a button which fires an event in the ViewModel. This event should enable a "Please Wait" spinner animation, do some processing which may take a few seconds, then hide the spinner. I'm not really sure of a good pattern I can use to make sure the spinner animation always appears.
As an example, I have a login process which does the following:
Displays spinner (set property on VM to true, spinner is bound to it)
Attempt to connect to server (can take a few seconds depending on connection)
On a failure, display a failure message
On success, save off some info about the user so it's available to the rest of the app.
What I'm finding is that the spinner never actually appears. I have tried wrapping the longer-running process in a Task.Run call, but that hasn't seemed to help.
Here's an approximation of what the code looks like:
// When true, spinner should be visible
protected bool _authenticatingIsVisible = false;
public bool AuthenticatingIsVisible
{
get { return _authenticatingIsVisible; }
set
{
_authenticatingIsVisible = value;
NotifyOfPropertyChange(() => AuthenticatingIsVisible);
}
}
public void Login()
{
try
{
AuthenticationIsVisible = true;
AuthCode result = AuthCode.NoAuthenticated;
Task.Run(() => { result = _client.Authenticate() }).Wait();
AuthenticationIsVisible = false;
if (result == AuthCode.Authenticated)
{
// Bit of misc. code to set up the environment
// Another check to see if something has failed
// If it has, displays a dialog.
// ex.
var error = new Error("Something Failed", "Details Here", Answer.Ok);
var vm = new DialogViewModel() { Dialog = error };
_win.ShowDialog(vm);
return;
}
else
{
DisplayAuthMessage(result);
}
}
finally
{
AuthenticationIsVisible = false;
}
}
The proper way would be not to block the UI thread (which is what you are doing right now with .Wait()), and use AsyncAwait instead.
private Task<AuthCode> Authenticate()
{
return Task.Run<AuthCode>(()=>
{
return _client.Authenticate();
});
}
public async void Login()
{
AuthenticationIsVisible = true;
AuthCode result = await Authenticate();
AuthenticationIsVisible = false;
}