Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - "TypeError: Cannot freeze" when dispatching an action

I'm working on an angular app that uses ngrx store & effects. I get

"TypeError: Cannot freeze"

error when dispatching an action from my component. I wrote it for a file upload feature. I think that I'm mutating the state but cannot figure out where and how to solve it. Here's my detailed code:

Actions:

export enum FileUploadActionTypes {
    UploadFile = '[File Upload] Upload File',
    UploadFileSuccess = '[File Upload] Upload File Success',
    UploadFileFailure = '[File Upload] Upload File Failure'
}

export class UploadFile implements Action {
    readonly type = FileUploadActionTypes.UploadFile;

    constructor(public payload: any) {}
}

export class UploadFileSuccess implements Action {
    readonly type = FileUploadActionTypes.UploadFileSuccess;

    constructor(public payload: any) {}
}

export class UploadFileFailure implements Action {
    readonly type = FileUploadActionTypes.UploadFileFailure;

    constructor(public payload: any) {}
}

export type FileUploadActionsUnion = UploadFile | UploadFileSuccess | UploadFileFailure;

Service:

export class FileUploadService {
    constructor(private httpClient: HttpClient) {

    }
    uploadFile(file: any): Observable<any> {
        return this.httpClient.post<any[]>('/api-url/', file);
    }
}

Effect:

@Effect()
uploadFile$ = this.actions$.pipe(
    ofType<UploadFile>(FileUploadActionTypes.UploadFile),
    mergeMap(action => this.fileUploadService.uploadFile(action.payload).pipe(
        map(result => new UploadFileSuccess(result)),
        catchError(error => of(new UploadFileFailure(error)))
    ))
)

Reducer:

export interface State {
    fileUploadSuccessResponse: any,
    fileUploadFailureResponse: any
}

export const initialState = {
    fileUploadSuccessResponse: null,
    fileUploadFailureResponse: null
}

export const getFileUploadSuccessResponse = state => state.fileUploadSuccessResponse;
export const getFileUploadFailureResponse = state => state.fileUploadFailureResponse;

export function reducer(state: State = initialState, action: FileUploadActionsUnion):State {
    switch(action.type) {
        case FileUploadActionTypes.UploadFile: {
            return {
                ...state
            }
        }
        case FileUploadActionTypes.UploadFileSuccess: {
            return {
                ...state,
                fileUploadSuccessResponse: action.payload
            }
        }
        case FileUploadActionTypes.UploadFileFailure: {
            return {
                ...state,
                fileUploadFailureResponse: action.payload
            }
        }
        default: {
            return state;
        }
    }
}
like image 408
Ahmet Ömer Avatar asked Sep 24 '18 10:09

Ahmet Ömer


1 Answers

It appears you are using the ngrx-store-freeze npm package. This package ensures that any updates you make to the store are not mutated directly. Part of this process is to apply an Object.freeze() to the payload object of the action you are attempting to dispatch. i.e. this.store.dispatch(new UploadFiles(file)).

Default behavior for the <input type="file"> control is to save it's value as a FileList object. Because this object is a primitive data type, Javascript will treat it as read-only, thus preventing you from writing to it's properties. This also explains why Object.freeze() fails, as it cannot apply a freeze to a read only data type.

I experienced the same issue recently using NGRX and the <input type="file"> form control. The fix was to clone the FileList into a new object before dispatching to the store.

onChange(control: FormControl) {
  const primitiveFileList: FileList = control.value;
  const clonedFiles = { ...primitiveFileList };
  this.store.dispatch(new UploadFiles(clonedFiles));
}

If anyone else knows any potential downfalls to this approach, I'd appreciate your input.

like image 198
jbiddick Avatar answered Oct 25 '22 17:10

jbiddick