I have a form that in this form create a new job. Behind a register job button in this form, I have a router link that rote to detail of new job that was created.
When clicking on this button, Router Link does not have an id of this job because the job's id does not set. After clicking on the register job button then the job's id is set.
How delay in router link until id of the job is set?
You need to use Promises
addJobToList(job): Promise<number> {
return new Promise((resolve, reject) => {
//
// Create job here;
//
resolve(job.id);
});
}
// <button (click)="createJob(formdata)" />
createJob(job): void {
this.addJobToList(job).then(jobid => {
this.router.navigate([`/jobdetails/${jobid}`]);
});
}
more info: https://codecraft.tv/courses/angular/es6-typescript/promises/
Related
I am using the following libraries in the relevant application: Angular 4.x, ngrx 4.x, rxjs 5.4.x
I have an api that I need to poll every 5 minutes. The user is also able to manually refresh the data. That data is stored in an ngrx store. I am using ngrx effects so the data is retrieved by dispatching an action of type 'FETCH'.
I want to setup a rxjs stream where it will dispatch the 'FETCH' action to the ngrx store. It will be a sliding 5 minute timer that resets when the user manually updates the store. The stream should initially emit a value when subscribed.
I'm not sure how I can reset the timer. In plain javascript I would do something like the following:
console.clear();
let timer;
let counter = 0;
function fetch() {
console.log('fetch', counter++);
poll();
}
function poll() {
if (timer != null) {
window.clearTimeout(timer);
}
timer = window.setTimeout(() => {
console.log('poll');
fetch();
}, 5000);
}
function manualGet() {
console.log('manual');
fetch();
}
fetch();
<button onClick="manualGet()">Get Data</button>
Question: How do I emit on an interval that is reset when another stream emits like the example again?
You want two components to your stream – a timer and some user input. So let's start with the user input. I'll assume some button which can be clicked:
const userInput$ = Observable.fromEvent(button, 'click');
Now we want to start a timer which resets everytime userInput$ emits. We can do that using
userInput$.switchMap(() => Observable.timer(0, 5000));
However, we also want this stream to start without the user having to first click the button. But that's also not a problem:
userInput$.startWith(null);
Now we put it all together:
Observable.fromEvent(button, 'click')
.startWith(null)
.switchMap(() => Observable.timer(0, 5000))
.subscribe(() => dispatchFetch());
Note that I am following your examples of using a 5 second timer, not a 5 minute timer (which you mentioned in the question.)
After writing it out in vanilla JS I realized that the source of the timer should be the data. I was struggling to figure out what the source would be. Clearly it couldn't be the timer since I needed to reset it.
I'm open to better options but here is how I solved it:
console.clear();
let counter = 0;
const data = new Rx.BehaviorSubject(null);
function fetch() {
data.next(counter++);
}
function manualGet() {
console.log('manual');
fetch();
}
// setup poll
data.switchMap(() => Rx.Observable.timer(5000))
.subscribe(() => {
console.log('poll');
fetch();
});
// subscribe to the data
data.filter(x => x != null).
subscribe(x => { console.log('data', x); });
// do the first fetch
fetch();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.5/Rx.min.js"></script>
<button onClick="manualGet()">Get Data</button>
With ngrx I am listening for the success action related to the fetch event.
in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)
For some reason an RSVP promise I created in an action stopped working for me and I can't figure out what when wrong where to make it stop working.
I have a component that sends an action and waits for a response
// COMPONENT IN ITEM ROUTE
callAjaxAction() {
this.setProperties({working:true});
Ember.RSVP.cast(this.attrs.action()).finally(() => {
Ember.$('.draggable').animate({
left: 0
});
this.setProperties({working:false});
});
}
This particular instance of the component calls this action in the controller
// IN ITEM CONTROLLER
placeBid() {
return new Ember.RSVP.Promise((resolve,reject) => {
if(this.get('winning')) {
this.get('notification')._confirm({message: "You are currently the highest bidder, would you like to continue with your bid?",isConfirm: true}).then(()=>{
console.log('confirmed');
this.send('placeBidActual').then(()=>{
resolve();
}).catch(()=>{
reject();
});
return true;
}).catch(()=>{
resolve();
console.log('denied');
return false;
});
} else {
setTimeout(()=>{
this.send('placeBidActual').then(()=>{
resolve();
}).catch(()=>{
reject();
});
},500);
}
});
}
This action is calling a confirm method on a service and waiting for the user to hit yes in the case of the user already winning this item. Otherwise I'm just calling the actual ajax action right away, that action looks like this
// IN ITEM CONTROLLER
placeBidActual() {
return new Ember.RSVP.Promise((resolve,reject) => {
Ember.$.ajax({
...
}).then((response)=>{
(do some stuff with the response)
resolve();
}, (reason)=>{
(do something with rejection reason)
reject(reason);
});
});
}
In the console I'm getting the error
Uncaught TypeError: Cannot read property 'then' of undefined
On the line where it states this.send('placeBidActual')
UPDATE:
Here is maybe a better explanation to the expected process flow.
The user attempts to place a bid, the user swipes a component over to indicate they wish to bid. The UI at this point shows a loading indicator and waits for the ajax action to complete before it resets the UI.
If the user is not already the highest bidder of the item it will go straight to the ajax action and upon completion signifies to the component to reset the UI.
However, if the user is the highest bidder of the item it should instead show a confirm message (using the notification service I have setup) and wait for the user to either confirm or deny. If they deny it just cancels and signifies to the component to reset the UI. If the user confirms then it calls the ajax action and upon completion signifies to the component to reset the UI.
Updated answer:
This isn't working because send doesn't return the value of the action.
I suggest moving the placeBidActual action to a method on the controller and call it like any normal method. This way you will get the return value and be able to call .then on it.
You should pass a function, whithout invoke it.
Instead this:
Ember.RSVP.cast(this.attrs.action()).finally(() =>
Try it:
Ember.RSVP.cast(this.attrs.action).finally(() =>
Invoking the funcion this.attrs.action() does it pass undefined for the Promise.
I need to update the context after an ajax request has finished. I'm using the flux architecture and everything works to the point that when my component is notified about the updated I need to set the new context.
A simple demostration:
I have a parent component which generates the context by calling a store. The store gets the data after an ajax request is initialized somewhere else. Like this:
RowAPI.ajaxGetAllRows();
Then I have my component which holds the context:
let ParentComponent = React.createClass({
childContextTypes: {
rows: React.PropTypes.object
},
getChildContext: function() {
return {
rows: RowStore.getAllRows(),
};
},
componentDidMount: function() {
RowStore.addChangeListener(this._onRowsChanged);
},
componentWillUnmount: function() {
RowStore.removeChangeListener(this._onRowsChanged);
},
render() {
return (
<ChildComponent />
);
},
_onRowsChanged: function() {
//Now we need to update context
}
});
Now since we are listening for row changes, we will get an update when our ajax request has finished and put the data into our store. Now we need to get that data and set it as context. That is the problem.
This is my child component that uses the context. I know that I just can pass the rows as a props to my child but this is just an example and in my real scenario I have many children which would need to pass the props.
let ChildComponent = React.createClass({
contextTypes: {
rows: React.PropTypes.object
},
render() {
return (
<div style={styles.wrapper}>
{this.context.rows}
</div>
);
},
});
Thanks in advance!
I would change the getChildContext in ParentComponent to refer to the state instead of a function call to the RowStore.
getChildContext: function() {
return {
rows: this.state.rows,
};
}
Then, whenever a row changes, and the _onRowsChanged callback it called, it can set this.state.rows accordingly.
I believe that the issue with the original method of calling RowStore.getAllRows() inside getChildContext is that it is only called once. Nothing is forcing it to call RowStore.getAllRows() on every change.
However, by using a state, you can use Flux concepts to "force" a change in state on every update, and that will be reflected in the context.
Is it possible to open an url when an event is clicked? I want to route my users to an url contains details related to event.
Yes, you can. Just handle the change event of your scheduler like this:
$("#scheduler").kendoScheduler({
change: function (e) {
e.preventDefault(); // prevent the default action
if (e.events.length > 0) { // if true then user clicked an event, not an empty slot
window.location.href = 'stackoverflow.com'; // replace with the desired url
}
},
// ... set other properties ...
});
The e.events is an array that has list of clicked events.
Kendo Scheduler Change Event