Understanding Ngrx OnRunEffects - ngrx

I have several Effect listeners that need to run until the user has logged out. As such, I'm trying to implement OnRunEffects.
A segment of my user.actions.ts is:
export const LOAD_USER = '[Users] Load User';
export class LoadUser implements Action {
readonly type = LOAD_USER;
}
export type UserActions = LoadUser;
I'm trying to implement OnRunEffects as:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(AuthActions.LOGOUT_SUCCESS)
}
However, takeUntil is complaining that it needs as Observable. So I tried:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(
Observable.of(new AuthActions.LogoutSuccess())
)
}
That in turn, causes the effect to not fire. From reading the code, it looks like it states:
When you see these actions
run the effects
until you see this other action.
What am I doing wrong?

Correct me if I'm wrong, but you might not want to run until the user has logged out, you might just want to run those effects if the user is still logged in. If yes, then instead of thinking to takeUntil:
#Effect()
someEffect$: Observable<Action> = this.actions$
.ofType<Your_Type>(Your_Action)
.withLatestFrom(this.store$.select(state => state.users.isUserLoggedIn))
.filter(([action, isUserLoggedIn]) => isUserLoggedIn)
// here, you'll receive the actions only is the user is logged in
// ... your code for the effect

Try this:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(this.actions.ofType(AuthActions.LOGOUT_SUCCESS)))
}
Documentation have been updated :
https://github.com/ngrx/platform/commit/4b606d581d18866576b9632b0537953c2b6006c4

Related

Using Ngrx Effects Lifecycle hooks OnInitEffects (seems to emit infinitely)

I'm trying to learn a bit about effect lifecycle hooks introduced in Ngrx 7 and I'm not really understanding something that's happening. I've got an Angular app and I have the following in an effects class, however, the init$ observable is emitting values infinitely. I would have expected it to fire once and complete. I am somewhat new to observables as well. The documentation doesn't really help me out as there aren't really many examples. I could add a take(1), but I would like to understand why it continues to emit forever.
#Injectable()
export class AuthEffects implements OnInitEffects{
constructor(private actions$: Actions) {}
#Effect({dispatch: false})
login$ = this.actions$.pipe(
ofType<LoginAction>(AuthActionTypes.LoginAction),
tap(console.log)
);
#Effect({dispatch: false})
logout$ = this.actions$.pipe(
ofType<LogoutAction>(AuthActionTypes.LogoutAction),
tap(console.log)
);
#Effect()
init$ = this.actions$.pipe(
ofType<Action>('[Auth] Effects Init'),
tap(console.log)
);
ngrxOnInitEffects(): Action {
console.log('AuthEffects init\'d');
return { type: '[Auth] Effects Init'};
}
}
This is the expected behaviour - effects' primary usage is to act on some third party side effects. And you set ofType<Action> so it emits whenever any Action happens, while for ex:
#Effect({dispatch: false})
login$ = this.actions$.pipe(
ofType<LoginAction>(AuthActionTypes.LoginAction),
tap(console.log)
);
emits whenever LoginAction happens.

NGRX bulk effect of already defined single effect

So, Im working on an app with a concept of "Plans" and each plan you can add a comment. That part works fine, but it seems to fail and get confused if i try to run this in a loop.
The Action:
export class AddComment implements Action {
readonly type = CommentActionTypes.AddComment;
constructor(public payload: Comment) {}
}
export class AddCommentSuccess implements Action {
readonly type = CommentActionTypes.AddCommentSuccess;
constructor(public payload: Comment) {}
}
Effect
#Effect()
addComment$: Observable<Action> = this.actions$
.ofType<AddComment>(CommentActionTypes.AddComment).pipe(
switchMap(action => this.commentService.addComment(this.disciplineType, action.payload)),
map((comment: any) => new AddCommentSuccess(comment)),
catchError(err => of(new AddCommentFail(err)))
);
Implementation
What im struggling with is firing this off in rapid success/ I have a situation where I want to add a duplicate comment to multiple plans.
saveSet.forEach(x => {
comment.plan_id = x.id;
this.store.dispatch(this.buildAddCommentAction(comment));
});
For reference:
buildAddCommentAction(comment: DisciplineComment) : Action {
return new CommentActions.AddComment(comment);
}
What is Happening
If i have a list of 5 plans, and want to add a duplicate comment to all of them, Im only getting a successful response for the last item in the loop.
Now i know that is overly chatty, that is 5 separate client/service calls. What I cant figure out, its what the prescribed approach to this should be?
1.) A new BulkAddComment Action, effect, etc. Im loathe to do this becuase I have Comments, Concerns (similar in function and need), and one of each for every "discipline". Thatd be about 36 new effects and twice that in actions. A serious refactor is needed.
2.) Modify the actions and effects for 1 or multiple
3.)?
Thanks for input
This is because you're using the switchMap operator which will cancel the current running observable, in your case the service call.
You'll have to use concatMap or mergeMap. If the order is important use concatMap, if not use mergeMap because this will make your service calls in parallel.
For more info, watch this.

url not defined. Refactor to new versions of angularfire, angular

I have refactor problems because my code dosnt work to the new versions of angular and angularfire.
Error
The line: upload.url = uploadTask.snapshot.downloadURL; is undefined.
Code
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
// three observers
// 1.) state_changed observer
(snapshot) => {
// upload in progress
upload.progress = (uploadTask.snapshot.bytesTransferred / uploadTask.snapshot.totalBytes) * 100;
console.log(upload.progress);
},
// 2.) error observer
(error) => {
// upload failed
console.log(error);
},
// 3.) success observer
(): any => {
upload.url = uploadTask.snapshot.downloadURL; //?!?!UNDEFINED
upload.name = upload.file.name;
this.saveFileData(upload);
}
);
Questions
I had tried different solutions from stackoverflow but it dosnt really work. Most of the example is also more about how to retrieve the image but i want to set the variable upload.url to a value.
Another question:
I'm new to angular and web. Will it take long time to change it to firestore? The code is based on realtime firebase.
To get the downloadURL, you have to call the getDownloadURL() method of the Storage Reference Object.
Try this:
uploadTask.snapshot.ref.getDownloadURL()
.subscribe(url => console.log(url))

EmberFire: Getting property generated by Cloud Function when saving record completes

I use a Cloud Function to generate a short unique URL on a record on the 'onWrite' event, and save it. This works well, but when I save a record from my Ember app using EmberFire, I do get a model back as an argument to a callback, but the URL of this model is undefined. Is there a way to return this back to the client? Or do I need to query the record to get the generated URL?
This is how my Cloud Function code looks:
exports.generateUrl = functions.database.ref('/tournaments/{tid}')
.onWrite(event => {
if (event.data.previous.exists()) {
return;
}
if (!event.data.exists()) {
return;
}
const url = shortid.generate();
return event.data.ref.update({ url });
});
Here is my component that saves data through form submission. I'm using an add-on called ember-changeset to handle some validations, but this shouldn't be related to the issue.
export default Ember.Component.extend({
submit(e) {
e.preventDefault();
let snapshot = this.changeset.snapshot();
return this.changeset
.cast(Object.keys(this.get('schema')))
.validate()
.then(() => {
if (this.changeset.get('isValid')) {
return this.changeset
.save()
.then((result) => {
// Here, result.get('url') is undefined.
})
}
})
}
});
If you have a function that writes new data back to a location in the database after a write, you'll have to keep listening to that location on the client in order to get that data back. Don't use a one-time read (once()), use a persistent listener (on()), and in that listener, make sure you're getting the URL or whatever you expect to be generated by the function. Then remove that listener if you don't need it any more.
(Sorry, I don't know Ember or what abstractions it provides around Realtime Database - I'm giving you the plain JavaScript API methods you'd use on a reference.)

Data validation using RxJS

I have the following function that validates that rangeFrom is not superior to rangeTo and that the rangeFrom does not already exist in the list of ranges.
How can rewrite this using RxJS?
const isTagAlreadyExist = (tags, currentTag) => _(tags)
.filter(x => x.id !== currentTag.id)
.some(x => _.inRange(currentTag.rangeTo, x.rangeFrom, x.rangeTo))
.value();
const validateRangeFrom = (tags, currentTag) => {
const errors = {};
if (isNumeric(currentTag.rangeFrom)) {
if (!_.inRange(currentTag.rangeFrom, 0, currentTag.rangeTo)) {
errors.rangeFrom = 'FROM_TAG_CANNOT_BE_GREATER_THAN_TO_TAG';
} else if (isTagAlreadyExist(tags, currentTag)) {
errors.rangeFrom ='TAG_ALREADY_EXISTS';
}
}
return {
errors
};
};
The question is: what parts do you want to rewrite to rxjs? Those are two pure functions that run synchronously from what I can see, I do not really see much a usecase for rxjs here - of course you could always utilize your functions within an rxjs stream:
const validateRangeFrom$ = (tags, currentTag) => {
return Observable.of(currentTag)
.map(tag => validateRangeFrom(tags, tag));
}
validateRangeFrom$(myTags, currentTag)
.subscribe(errors => console.log(errors));
But as you can see, this does not make much sense if you simply wrap it inside a stream, the essence of useful reactive programming is, that everything is reactive, not just some small parts, so for your example, you should start with having tags$ and currentTag$ as observables - let's assume that you have that, then you could do something like:
const tags$: Observable<ITag[]>... // is set somewhere, and emits a new array whenever it is changed
const currentTag$: Observable<ITag>... // is set somewhere and emits the tag whenever a new currentTag is set
const validateRangeFrom$ = Observable
.combineLatest(tags$, currentTag$, (tags, tag) => ({tags, tag}))
.map(({tags, tag}) => validateRangeFrom(tags, tag));
validateRangeFrom$.subscribe(errors => console.log(errors));
This will automatically trigger the validation for you whenever a new tags-array is emitted or a new currentTag is selected/set - but again: your validation-method is kept the same - as even in reactive programming you have to do validation and logic-operations at some point, the reactive part usually just concerns the flow of the data (see: tags$ and currentTag$)

Resources