Search code examples
angularngrxreducers

angular @ngrx - redux where should i handle actions like export that don't change the state


I have an APP with an "Export" button. When clicked , the APP get from the server a big text file and uses the HTML5 blob API to "save as" so the user can save it to his disk

In order to start the export operation in dispatch export action

this.store.dispatch(new scanListsActions.Export(id));

this action is then handled by the "effects" code that goes to the backend server to grab the file. when done, ExportSuccess action is dispatched.

Now, i don't want to store the file content in the store since it is very big, on the other hand i need the data in the view in order to send it to the user. currently you can see that i send it to the user in the reducer code (below).

How normally is it done? where is the place to hold the that sends the file to the user?

import * as fileSaver from 'file-saver'; // npm i --save file-saver

export function reducer(state: ScanListsState = initialState, action: ScanListsActions): ScanListsState {
    switch (action.type) {
        case ScanListsActionTypes.Export:
            return {
                ...state,
                loading: true
            };

        case ScanListsActionTypes.ExportSuccess:

       // these 2 lines below send the file to the user (starts a download)
       // where should i put this code. I don't think the reducer is the place for it

            const blob = new Blob([action.payload.data], { type: 'text/plain; charset=utf-8' });
            fileSaver.saveAs(blob, 'destination_list.txt');

            return {
                ...state,
                loading: false,
                error: null
            };

        case ScanListsActionTypes.GeneralFail:
            return {
                ...state,
                loading: false,
                error: action.payload
            };
        default:
            return state;
    }
}

Effects code

@Injectable()
export class ScanListsEffects {

    constructor(private scanListService: ScanListService,
        private actions$: Actions) { }



    @Effect()
    exportScanList$: Observable<Action> = this.actions$.pipe(
        ofType(scanListsActions.ScanListsActionTypes.Export),
        mergeMap((action: scanListsActions.Export) =>
            this.scanListService.getScanList(action.payload).pipe(
                map(sList => (new scanListsActions.ExportSuccess(sList))),
                catchError(err => of(new scanListsActions.GeneralFail(err)))
            )
        )
    );
}

Solution

  • You should make a service call for your saving, just as you did for your read. In an effect you'd call the service:

    Something like:

        @Effect()
        fileSaveExportScanList$: Observable<Action> = this.actions$.pipe(
            ofType(scanListsActions.ScanListsActionTypes.ExportSuccess),
            mergeMap((action: scanListsActions.ExportSuccess) =>
                this.scanListService.saveScanList(action.payload).pipe(
                    map (() => (new scanListsActions.SaveSuccess())),
                    catchError(err => of(new scanListsActions.GeneralFail(err)))
                )
            )
        );
    

    and then have a service call 'saveScanList'

         saveScanList(data: any){
                const blob = new Blob([data], { type: 'text/plain; charset=utf-8' });
                fileSaver.saveAs(list, 'destination_list.txt');