Bir ağaç veri yapısını sorgulamak için rxjs'in diğer operatörleri ile birlikte seçme işlecini kullanmanın bir yolunu bulmaya çalışıyorum. düz liste) ChangeDetectionStrategy.OnPush semantiklerinin referans bütünlüğünü koruyacak şekilde, ancak en iyi girişimlerim ağacın herhangi bir kısmı değiştiğinde tüm ağacın yeniden işlenmesine neden olur. Herhangi bir fikri olan var mı? mağazadan verilerin temsilcisi olarak aşağıdaki arayüz düşünün:Açısal 2, ngrx/mağaza, RxJS ve ağaç benzeri veriler
export interface TreeNodeState {
id: string;
text: string;
children: string[] // the ids of the child nodes
}
export interface ApplicationState {
nodes: TreeNodeState[]
}
aşağıdaki arayüzü uygulayan nesnelerin bir grafiğini dönmek için yukarıdaki durumunu denormalizes bir seçici oluşturmanız gerekir :
export interface TreeNode {
id: string;
text: string;
children: TreeNode[]
}
İdeal olarak, grafiğin herhangi bir parçasının yalnızca, herhangi bir düğüm değiştiğinde tamamen yeni bir grafik döndürmek yerine, değiştiyse çocuklarını güncellemesini istiyorum. Ngrx/store ve rxjs kullanarak böyle bir seçicinin nasıl oluşturulacağını bilen var mı?
// 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
));
}
Bunu uygulamak için hiç şansınız oldu mu? Uygulamamda aynı şartı yaşıyorum. –
"ApplicationState.nodes" öğesinin ebeveynlerin çocuk düğümlerinden önce ana düğümleri olduğunu varsayabilir miyiz? – 0xcaff
Ayrıca, 'OnPush' değişiklikleri yalnızca bir özellik referansı değiştirildikten sonra yayılır (veya 'markForCheck()' çağrılır, ancak bu, tüm bileşeni güncelleştirir). Bu, dizinin referansını güncellemeniz ve yeniden yapılandırmanın tüm ağacın kontrol edilmesine neden olmanız anlamına gelir. OnPush yerine, muhtemelen Immutable.js'yi kullanmak istersiniz, ancak bunun açısal olarak tam olarak nasıl çalıştığından emin değilim. – 0xcaff