example español javascript angular rxjs ngrx

javascript - español - ngrx store angular 6



Angular 2, ngrx/store, RxJS y datos tipo árbol (1)

He estado tratando de encontrar una manera de utilizar el operador de selección en combinación con los otros operadores de rxjs para consultar una estructura de datos de árbol (normalizada en la tienda a una lista plana) de tal manera que conserve la integridad referencial para ChangeDetectionStrategy. semántica, pero mis mejores intentos hacen que todo el árbol se vuelva a representar cuando cambie alguna parte del árbol. ¿Alguien tiene alguna idea? Si considera que la siguiente interfaz es representativa de los datos en la tienda:

export interface TreeNodeState { id: string; text: string; children: string[] // the ids of the child nodes } export interface ApplicationState { nodes: TreeNodeState[] }

Necesito crear un selector que desnormalice el estado anterior para devolver un gráfico de objetos implementando la siguiente interfaz:

export interface TreeNode { id: string; text: string; children: TreeNode[] }

Es decir, necesito una función que tome un <ApplicationState> observable y devuelva un <TreeNode []> observable de manera que cada instancia de TreeNode mantenga la integridad referencial a menos que uno de sus hijos haya cambiado .

Lo ideal sería que cualquier parte del gráfico solo actualice a sus hijos si han cambiado en lugar de devolver un gráfico completamente nuevo cuando cambia cualquier nodo. ¿Alguien sabe cómo se podría construir un selector de este tipo utilizando ngrx / store y rxjs?

Para obtener ejemplos más concretos de los tipos de cosas que he intentado, consulte el fragmento de código a continuación:

// This is the implementation I''m currently using. // It works but causes the entire tree to be rerendered // when any part of the tree changes. export function getSearchResults(searchText: string = '''') { return (state$: Observable<ExplorerState>) => Observable.combineLatest( state$.let(getFolder(undefined)), state$.let(getFolderEntities()), state$.let(getDialogEntities()), (root, folders, dialogs) => searchFolder( root, id => folders ? folders.get(id) : null, id => folders ? folders.filter(f => f.parentId === id).toArray() : null, id => dialogs ? dialogs.filter(d => d.folderId === id).toArray() : null, searchText ) ); } function searchFolder( folder: FolderState, getFolder: (id: string) => FolderState, getSubFolders: (id: string) => FolderState[], getSubDialogs: (id: string) => DialogSummary[], searchText: string ): FolderTree { console.log(''searching folder'', folder ? folder.toJS() : folder); const {id, name } = folder; const isMatch = (text: string) => !!text && text.toLowerCase().indexOf(searchText) > -1; return { id, name, subFolders: getSubFolders(folder.id) .map(subFolder => searchFolder( subFolder, getFolder, getSubFolders, getSubDialogs, searchText)) .filter(subFolder => subFolder && (!!subFolder.dialogs.length || isMatch(subFolder.name))), dialogs: getSubDialogs(id) .filter(dialog => dialog && (isMatch(folder.name) || isMatch(dialog.name))) } as FolderTree; } // This is an alternate implementation using recursion that I''d hoped would do what I wanted // but is flawed somehow and just never returns a value. export function getSearchResults2(searchText: string = '''', folderId = null) : (state$: Observable<ExplorerState>) => Observable<FolderTree> { console.debug(''Searching folder tree'', { searchText, folderId }); const isMatch = (text: string) => !!text && text.search(new RegExp(searchText, ''i'')) >= 0; return (state$: Observable<ExplorerState>) => Observable.combineLatest( state$.let(getFolder(folderId)), state$.let(getContainedFolders(folderId)) .flatMap(subFolders => subFolders.map(sf => sf.id)) .flatMap(id => state$.let(getSearchResults2(searchText, id))) .toArray(), state$.let(getContainedDialogs(folderId)), (folder: FolderState, folders: FolderTree[], dialogs: DialogSummary[]) => { console.debug(''Search complete. constructing tree...'', { id: folder.id, name: folder.name, subFolders: folders, dialogs }); return Object.assign({}, { id: folder.id, name: folder.name, subFolders: folders .filter(subFolder => subFolder.dialogs.length > 0 || isMatch(subFolder.name)) .sort((a, b) => a.name.localeCompare(b.name)), dialogs: dialogs .map(dialog => dialog as DialogSummary) .filter(dialog => isMatch(folder.name) || isMatch(dialog.name)) .sort((a, b) => a.name.localeCompare(b.name)) }) as FolderTree; } ); } // This is a similar implementation to the one (uses recursion) above but it is also flawed. export function getFolderTree(folderId: string) : (state$: Observable<ExplorerState>) => Observable<FolderTree> { return (state$: Observable<ExplorerState>) => state$ .let(getFolder(folderId)) .concatMap(folder => Observable.combineLatest( state$.let(getContainedFolders(folderId)) .flatMap(subFolders => subFolders.map(sf => sf.id)) .concatMap(id => state$.let(getFolderTree(id))) .toArray(), state$.let(getContainedDialogs(folderId)), (folders: FolderTree[], dialogs: DialogSummary[]) => Object.assign({}, { id: folder.id, name: folder.name, subFolders: folders.sort((a, b) => a.name.localeCompare(b.name)), dialogs: dialogs.map(dialog => dialog as DialogSummary) .sort((a, b) => a.name.localeCompare(b.name)) }) as FolderTree )); }


Si está dispuesto a repensar el problema, puede usar Rxjs operator scan :

  1. Si no existe ApplicationState anterior, acepte el primero. Traducirlo a TreeNodes recursivamente. Como este es un objeto real, rxjs no está involucrado.
  2. Cada vez que se recibe un nuevo estado de aplicación, es decir, cuando se activa el escaneo, implemente una función que mute los nodos anteriores utilizando el estado recibido y devuelve los nodos anteriores en el operador de escaneo. Esto te garantizará integridad referencial.
  3. Es posible que ahora se quede con un nuevo problema, ya que los cambios en los nodos de árbol mutados podrían no ser detectados. Si es así, busque la pista haciendo una firma para cada nodo, o considere agregar un changeDetectorRef a un nodo (proporcionado por el nodo de representación del componente), lo que le permite marcar un componente para actualizar. Esto probablemente tendrá un mejor desempeño, ya que puede utilizar la estrategia de detección de cambios OnPush .

Pseudocódigo

state$.scan((state, nodes) => nodes ? mutateNodesBy(nodes, state) : stateToNodes(state))

Se garantiza que la salida preservará la integridad referencial (donde sea posible) ya que los nodos se crean una vez, y luego solo se mutan.