Chain Actions in an Effect ngrx v8 - ngrx-effects

I want to chain effects with the latest syntax of ngrx. I've searched and found this question on stackoverflow but it has old syntax.
This is the current effect:
export class DeleteCommentEffect {
deleteComment$ = createEffect(() =>
this.actions$.pipe(
ofType(DeletingComment),
mergeMap((action) => this.commentService.deleteComment(action.dossierId, action.commentId)
.pipe(
map((statusCode: number) => {
return DeleteCommentSuccess({ statusCode });
}),
catchError((error: HttpErrorResponse) => {
return of(DeleteCommentError({ error }));
})
))
)
);
constructor(
private actions$: Actions,
private commentService: DBCommentService) {
}
}
I want to chain this effect after successfully delete a comment.
export class GetCommentEffects {
getComment$ = createEffect(() =>
this.actions$.pipe(
ofType(GettingComment),
mergeMap(action =>
this.commentService.getAllComments(action.dossierId).pipe(
map((comments: Comment[]) => {
return GetCommentSuccess({comments});
}),
catchError((error: HttpErrorResponse) => {
return of(GetCommentError({error}));
})
))
)
);
constructor(
private actions$: Actions,
private commentService: DBCommentService
) {}
}
I've searched in ngrx docs but it seems like it does not mention about how to chain effects.

Create an effect that listens for DeleteCommentSuccess action that dispatches GettingComment.
deleteCommentSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(DeleteCommentSuccess),
map(() => CommentActions.GettingComments())
)
);

Related

Redux toolkit createAsyncThunk using Parameter

I need your help.
We implemented createAsyncThunk using the Redux Toolkit.
However, as shown in the picture, I need a function to change ChannelId flexibly. Can't I use the parameter in createAsyncThunk?
How can I use it if I can?
Or is there any other way?
I am sorry that the quality of the question is low because I am Korean using a translator.
enter image description here
// ACTION
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (data, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
// SLICE
const youtube_PlaylistSlice = createSlice({
name: "YOUTUBE_PLAYLIST",
initialState,
reducers: {},
// createAsyncThunk 호출 처리 = extraReducers
extraReducers(builder) {
builder
.addCase(getYoutubeList_PlayList.pending, (state, action) => {
state.loading = true;
})
.addCase(getYoutubeList_PlayList.fulfilled, (state, action: PayloadAction<youtubeResponse>) => {
state.loading = false;
state.data = action.payload;
})
.addCase(getYoutubeList_PlayList.rejected, (state, action: PayloadAction<any>) => {
state.error = action.payload;
});
},
});
You named both the incoming argument data as well as the result of your axios call. That will "shadow" the original data and you cannot access it any more. Give those two variables different names.
Here I called it arg, which allows you to access arg.channelId.
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (arg, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${arg.channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
You would now dispatch this as dispatch(getYoutubeList_PlayList({ channelId: 5 }))

PWA cache gets deleted on redirects?

Both the static and dynamic cache get deleted from the browser when you want to visit another link. So if you go offline and try to make a request you then get an error page instead of a cached response. If you then click the back button in browser and then the forward button the cache comes back and it works. I might have something wrong with verifying the cache on activation?
This is my service worker:
const staticCacheName = "CacheV1";
const dynamicCacheName = "DynamicCacheV1";
const dynamicCacheLimit = 18;
const assets = [
'/',
'/css/main_styles.css',
'/js/ui.js',
'/js/jquery-3.6.0.slim.min.js',
'/icons/search.svg',
'/icons/favicon.svg',
'/img/bg.png',
'/img/og.png',
'/img/generated.svg',
'/icons/at.svg',
'/icons/heartFull.svg',
'/icons/comment.svg',
'/icons/share.svg',
'/icons/report.svg',
'/manifest.json',
'/fonts/titillium-web-300.woff2',
/* ... */
];
// This just deletes access dynamic cache
const limitCacheSize = (name, size) => {
caches.open(name).then(cache => {
cache.keys().then(keys => {
if(keys.length > size) {
cache.delete(keys[0]).then(limitCacheSize(name, size));
}
});
});
}
// Install service worker
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then(cache => {
cache.addAll(assets);
})
);
});
// Activate event
self.addEventListener('activate', evt => {
evt.waitUntil(
caches.keys().then(keys => {
return Promise.all( keys
.filter(key => key !== staticCacheName && key !== dynamicCacheName)
.map(key => caches.delete(key))
)
})
)
});
// Fetch event
self.addEventListener('fetch', evt => {
evt.respondWith(
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request).then(fetchRes => {
return caches.open(dynamicCacheName).then(cache => {
if (evt.request.headers.has('range')) {
return cacheRes;
} else {
cache.put(evt.request.url, fetchRes.clone());
limitCacheSize(dynamicCacheName, dynamicCacheLimit);
return fetchRes;
}
});
});
}).catch(() => {
if (!/./.test(evt.request.url)) {
return caches.match('/img/fallback.html');
} else if(/.jpeg|.jpg|.png|.webp/.test(evt.request.url)) {
return caches.match('/img/fallbackImage.png');
} else if(/.gif/.test(evt.request.url)) {
return caches.match('/img/fallbackImage.png');
} else if(/.mp3|.ogg|.aac|.wav/.test(evt.request.url)) {
return caches.match('/img/beepBoop.mp3');
}
})
)
});

How to dynamically add parameters to firestore query and map to object

What is the recommended way to map the data to an object and return it as a promise/observable while being able to add dynamic/conditional parameters to the query.
In getCompanies2 I can dynamically add parameters to the query but I can't figure out how to map the data returned to my object and return it as a promise/observable.
In getCompanies everything works as I want it but I have to duplicate the code (as below) if I have dynamic query parameters to add.
Note: convertDocTimeStampsToDate just does what it says. I have excluded it to reduce the size of the code section.
getCompanies(searchText: string, useWhereActive: boolean): Observable<Company[]> {
if (useWhereActive) {
return this.db.collection('companies', ref => ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff')
.where('active', '==', true)
)
.snapshotChanges()
.pipe(
map(snaps => convertSnaps<Company>(snaps)),
first()
);
} else {
return this.db.collection('companies', ref => ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff')
)
.snapshotChanges()
.pipe(
map(snaps => convertSnaps<Company>(snaps)),
first()
);
}
}
​
getCompanies2(searchText: string, useWhereActive: boolean) {
let query = this.db.collection('companies').ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff');
​
if (useWhereActive) {
query.where('active', '==', true);
}
​
query.get().then(querySnapshot => {
const results = this.convertDocuments<Company>(querySnapshot.docs);
console.log(results);
});
}
convertDocuments<T>(docs) {
return <T[]>docs.map(doc => {
return {
id: doc.id,
...doc.data()
};
});
}
export function convertSnaps<T>(snaps) {
return <T[]>snaps.map(snap => {
const data = convertDocTimeStampsToDate(snap.payload.doc.data());
return {
id: snap.payload.doc.id,
...data
};
});
}
I got it to work like below, I guess I am still getting my head around promises.
Any better solutions will be accepted though as I am still learning and don't know if this is the best method.
getCompanies2(searchText: string, useWhereActive: boolean) {
let query = this.db.collection('companies').ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff');
if (useWhereActive) {
query.where('active', '==', true);
}
return query.get().then(querySnapshot => {
return this.convertDocuments<Company>(querySnapshot.docs);
});
}

How I can call Observable.complete callback function? It doesn't work

In ionic 2, (and angularfire2#4.0.0-rc.0, firebase#3.9.0)
I tried this code, but complete function doesn't work!
constructor(public af: AngularFireDatabase, ...) {
this.temp = af.list('/myTest').subscribe(data => {
... // my tastks
}, error => {
console.log(error);
}, () => {
console.log("done!"); // I can't see this output!
});
}
I already tried very many times, but I can't see "done".
In another function, I tried this but the result is same with first.
console.log(this.temp.closed); // false
this.temp.unsubscribe()
console.log(this.temp.closed); // true
,, What can I do..?
AngularFire list and object observables do not complete. Their internal Firebase refs remain connected, listening for changes to the database.
If you want the list to complete after the first value is received, you can use the first operator:
import "rxjs/add/operator/first";
constructor(public af: AngularFireDatabase, ...) {
this.temp = af.list('/myTest')
.first()
.subscribe(data => {
... // my tastks
}, error => {
console.log(error);
}, () => {
console.log("done!");
});
}
Note that complete handlers are not called upon unsubscription. They are called when the observable completes.
If you want a callback to be invoked upon either completion or unsubscription, you can use the finally operator instead:
import "rxjs/add/operator/finally";
constructor(public af: AngularFireDatabase, ...) {
this.temp = af.list('/myTest')
.finally(() => {
console.log("done!");
})
.subscribe(data => {
... // my tastks
}, error => {
console.log(error);
});
}

Angular2 rxjs call to rest api and get undefined output

I have problem with my Angular2 service. My function call to rest server, next I would like to print output but console.log shows undefined.
Main class
export class EventListComponent implements OnInit {
events: Event[];
constructor(
private eventService: EventService
) {}
loadPosts() {
console.log('loading');
this.eventService.getEvents()
.subscribe(
events => this.events = events,
err => {
console.log(err);
});
console.log(this.events); // undefined
}
ngOnInit() {
// Load comments
this.loadPosts();
}
}
And EventService
#Injectable()
export class EventService {
private eventsUrl = 'http://localhost:5000/search';
constructor(private http: Http) {}
getEvents(): Observable<Event[]> {
return this.http.get(this.eventsUrl)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
}
Whats is wrong?
You're logging this.events before it is set. Do:
loadPosts() {
this.eventService.getEvents()
.catch(err => console.log(err))
.subscribe(
events => {
this.events = events;
console.log(this.events);
});
}