2016-08-23 19 views
23

मैं एक पेड़ डेटा संरचना से पूछने के लिए आरएक्सजे के अन्य ऑपरेटरों के साथ संयोजन में चयन ऑपरेटर का उपयोग करने का एक तरीका जानने का प्रयास कर रहा हूं (स्टोर में सामान्यीकृत फ्लैट सूची) इस तरह से कि यह ChangeDetectionStrategy.OnPush semantics के लिए संदर्भित अखंडता को बरकरार रखती है लेकिन मेरे सर्वोत्तम प्रयासों से पेड़ के किसी हिस्से में परिवर्तन होने पर पूरे पेड़ को फिर से प्रस्तुत किया जा सकता है। क्या किसी के पास कोई विचार है? आप की दुकान में डेटा के प्रतिनिधि के रूप में निम्नलिखित इंटरफेस पर विचार करें:कोणीय 2, एनजीआरएक्स/स्टोर, आरएक्सजेएस और पेड़ की तरह डेटा

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

मैं एक चयनकर्ता कि ऊपर राज्य denormalizes निम्नलिखित इंटरफेस को लागू वस्तुओं की एक ग्राफ वापस जाने के लिए बनाने की जरूरत :

export interface TreeNode { 
 
id: string; 
 
text: string; 
 
children: TreeNode[] 
 
}
है कि, मैं एक समारोह है कि एक हे लेता है की जरूरत है bservable < एप्लिकेशनस्टेट > और एक अवलोकन योग्य < ट्री नोड [] > जैसे प्रत्येक ट्री नोड इंस्टेंस रेफरेंसियल अखंडता को बनाए रखता है जब तक कि उसके बच्चों में से कोई एक बदल नहीं जाता है।

आदर्श रूप से मैं ग्राफ का कोई भी हिस्सा अपने बच्चों को अपडेट करना चाहता हूं अगर वे किसी भी नोड में परिवर्तन करते समय पूरी तरह से नए ग्राफ को वापस करने के बजाय बदल गए हैं। क्या कोई जानता है कि एनजीआरएक्स/स्टोर और आरएक्सजेएस का उपयोग करके इस तरह के चयनकर्ता का निर्माण कैसे किया जा सकता है?

चीजों के प्रकार मैं नीचे टुकड़ा की जाँच का प्रयास किया है की अधिक ठोस उदाहरण के लिए:

// 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 
 
      )); 
 
}

+0

क्या आपके पास इसे लागू करने का कोई भाग्य है? मुझे अपने ऐप में एक ही आवश्यकता है। –

+0

क्या हम इस धारणा को बना सकते हैं कि 'ApplicationState.nodes' के माता-पिता के नोड्स के सामने पैरेंट नोड्स हैं? – 0xcaff

+0

इसके अलावा, 'ऑनपश' परिवर्तनों के साथ केवल संपत्ति के संदर्भ के बाद प्रचारित किया जाता है (या 'markForCheck()' कहा जाता है, लेकिन यह पूरे घटक को अपडेट करता है)। इसका मतलब है कि आपको सरणी के संदर्भ को अद्यतन करना होगा, जिससे पूरे पेड़ को चेक किया जा सकेगा। ऑनपश के बजाय, आप शायद Immutable.js का उपयोग करना चाहते हैं, लेकिन मुझे यकीन नहीं है कि यह कोणीय के साथ कैसे काम करता है। – 0xcaff

उत्तर

1

यदि समस्या पर पुनर्विचार करने के लिए, आप Rxjs ऑपरेटर scan इस्तेमाल कर सकते हैं तैयार:

  1. यदि कोई पिछला अनुप्रयोगस्टेट मौजूद नहीं है, तो पहले को स्वीकार करें। इसे बार-बार ट्रीनोड्स में अनुवाद करें। चूंकि यह एक वास्तविक वस्तु है, आरएक्सजे शामिल नहीं है।
  2. जब भी कोई नया आवेदन राज्य प्राप्त होता है, यानी जब स्कैन आग, कि mutates पहले वाली स्थिति का उपयोग कर नोड्स प्राप्त एक समारोह को लागू करना तथा और पिछले नोड रिटर्न ऑपरेटर स्कैन में। यह आपको संदर्भित अखंडता की गारंटी देगा।
  3. अब आपको एक नई समस्या के साथ छोड़ा जा सकता है, क्योंकि उत्परिवर्तित पेड़ नोड्स में परिवर्तन नहीं उठाए जा सकते हैं। यदि ऐसा है, तो प्रत्येक नोड के लिए हस्ताक्षर करके track by देखें, या changeDetectorRef को नोड (घटक प्रतिपादन नोड द्वारा प्रदान किया गया) जोड़ने पर विचार करें, जिससे आप अपडेट के लिए एक घटक को चिह्नित कर सकें। यह संभवतः बेहतर प्रदर्शन करेगा, क्योंकि आप change detection strategy OnPush के साथ जा सकते हैं।

स्यूडोकोड:

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

उत्पादन रेफरेंशियल इंटिग्रिटी (जहां संभव हो) के रूप में नोड्स एक बार निर्माण कर रहे हैं, तो केवल उत्परिवर्तित संरक्षित करने के लिए गारंटी है।

संबंधित मुद्दे