Pass data to not-yet-loaded view in Aurelia

六月ゝ 毕业季﹏ 提交于 2019-12-11 06:31:43

问题


I am navigating from one view (call it bestSellersView) to another (BookDetailsView). There are multiple different 'parent' views that can navigate to 'Book Details' and they all need to pass the book that is to be viewed to the next view. I don't want to inject the source view to the details view as some threads suggest since my constructor would grow with each new view that uses the details sub-view.

I am trying to use the event aggregator, however due to the life cycle of things I am always getting a blank details screen on the first time I navigate. When I first navigate to the 'book details' view the ViewDetailsMessage has not yet been subscribed to before the publisher (best sellers) sends the message. Since I have my viewmodel set to singleton, the subsequent clicks work fine (since the details view is already constructed and subscribed to the event).

How can I get around this chicken-egg problem in Aurelia?

Edit 01

Here is what I was doing when I was having a problem:

Master.ts:

import { JsonServiceClient } from "servicestack-client";
import {
    ListPendingHoldingsFiles,
    ListPendingHoldingsFilesResponse,
    SendHoldings,
    PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";

@singleton()
@inject(Router, EventAggregator)
export class Pending {
    router: Router;
    positions: PositionFileInfo[];
    client: JsonServiceClient;
    eventAgg: EventAggregator;

    constructor(router, eventAggregator) {
        this.router = router;
        this.eventAgg = eventAggregator;
        this.client = new JsonServiceClient('/');
        var req = new ListPendingHoldingsFiles();
        this.client.get(req).then((getHoldingsResponse) => {
            this.positions = getHoldingsResponse.PositionFiles;
        }).catch(e => {
            console.log(e); // "oh, no!"
        });
    }

    openHoldings(positionInfo) {
        this.eventAgg.publish(new GetPendingPositionMessage(positionInfo));
        this.router.navigate('#/holdings');
    }
}

Child.ts:

import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import {
    GetPendingHoldingsFile,
    GetPendingHoldingsFileResponse,
    Position,
    PositionFileInfo
} from "../holdingsManager.dtos";

@singleton()
@inject(Router, EventAggregator)
export class Holdings {
    router: Router;
    pendingPositionFileInfo: PositionFileInfo;
    position: Position;
    client: JsonServiceClient;
    eventAgg: EventAggregator;

    constructor(router, eventAggregator) {
        this.router = router;
        this.eventAgg = eventAggregator;
        this.eventAgg.subscribe(GetPendingPositionMessage,
            message => {
                this.pendingPositionFileInfo = message.fileInfo;
            });
    }

    activate(params, routeData) {
        this.client = new JsonServiceClient('/');
        var req = new GetPendingHoldingsFile();
        req.PositionToRetrieve = this.pendingPositionFileInfo;
        this.client.get(req).then((getHoldingsResponse) => {
            this.position = getHoldingsResponse.PendingPosition;
        }).catch(e => {
            console.log(e); // "oh, no!"
        });
    }
}

Here is what I am doing now:

master.ts

import { JsonServiceClient } from "servicestack-client";
import {
    ListPendingHoldingsFiles,
    ListPendingHoldingsFilesResponse,
    PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";

@singleton()
@inject(Router, EventAggregator)
export class Pending {
    router: Router;
    eventAgg: EventAggregator;
    positions: PositionFileInfo[];
    client: JsonServiceClient;
    fileInfo: PositionFileInfo;

    constructor(router, eventAggregator) {
        this.router = router;
        this.eventAgg = eventAggregator;
        this.eventAgg.subscribe(GetPendingPositionMessage, () => {
            this.eventAgg.publish(new SetPendingPositionMessage(this.fileInfo));
        });
    }

    activate(params, routeData) {
        this.client = new JsonServiceClient('/');
        var req = new ListPendingHoldingsFiles();
        this.client.post(req).then((getHoldingsResponse) => {
            this.positions = getHoldingsResponse.PositionFiles;
        }).catch(e => {
            console.log(e); // "oh, no!"
        });
    }

    openHoldings(positionInfo) {
        this.fileInfo = positionInfo;
        this.router.navigate('#/holdings');
    }
}

child.ts

import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import {
    GetPendingHoldingsFile,
    GetPendingHoldingsFileResponse,
    Position,
    SendHoldings,
    PositionFileInfo
} from "../holdingsManager.dtos";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";
import { GetDeliveredPositionMessage } from "../common/GetDeliveredPositionMessage";
import { SetDeliveredPositionMessage } from "../common/SetDeliveredPositionMessage";

@singleton()
@inject(Router, EventAggregator)
export class Holdings {
    router: Router;
    pendingPositionFileInfo: PositionFileInfo;
    position: Position;
    client: JsonServiceClient;
    eventAgg: EventAggregator;

    constructor(router, eventAggregator) {
        this.router = router;
        this.eventAgg = eventAggregator;
        this.eventAgg.subscribe(SetPendingPositionMessage, message => this.getPositionData(message.fileInfo));
        this.eventAgg.subscribe(SetDeliveredPositionMessage, message => this.getPositionData(message.fileInfo));
    }

    getPositionData(fileInfo) {
        this.position = null;
        this.client = new JsonServiceClient('/');
        var req = new GetPendingHoldingsFile();
        req.PositionToRetrieve = fileInfo;
        this.client.post(req).then((getHoldingsResponse) => {
            this.position = getHoldingsResponse.PendingPosition;
        }).catch(e => {
            console.log(e); // "oh, no!"
        });
    }

    activate(params) {
        this.eventAgg.publish(new GetPendingPositionMessage());
        this.eventAgg.publish(new GetDeliveredPositionMessage());
    }

    sendHoldings() {
        var req = new SendHoldings();
        this.client.get(req).then((sendHoldingsRepsonse) => {
            console.log("SUCCESS!"); // "oh, no!"
        }).catch(e => {
            console.log(e); // "oh, no!"
        });
    }
}

I need to add a bit of logic to the activate method of the child to ensure I ask for the right parents holdings file.


回答1:


Sounds like you need to share state between views. I use a StateStore class that is injected into any views that wish to share state. By default all objects injected are Singletons making it easy to share state. A very simple example could be (in TypeScript):

statestore.ts

export class StateStore {
    state: any;
}

masterview.ts

autoinject()
export class MasterView {
    constructor(private store: StateStore){
    }

    doSomething(): void {
        this.store.state = "some value";
        // navigate to detail view
    }
}

detailview.ts

autoinject()
export class DetailView {
    sharedValue: any;

    constructor(store: StateStore) {
        this.sharedValue = store.state;
    }
}

This will share a single instance of StateStore between views allowing state to easily be shared.




回答2:


My current solution, though not as pretty as I'd like it to be is as follows:

Source view (bestSellersView) is a singleton and subscribes to "GetCurrentBookMessage". When a user selects a book, the Source saves it locally and navigates to the "BookDetailsView". The BookDetailsView is constructed, subscribes to a "SetCurrentBookMessage" and, when activated, it sends a GetCurrentBookMessage. The source view answers with a "SetCurrentBookMessage".

This will get messy with multiple sources and I will have to have some way to resolve where the navigation came from to pick the 'right' source, but for today this works.

Edit 01 I have also tried getting rid of all the event aggregator stuff and putting this in the master's OpenHoldings method:

    let routeConfig = this.router.routes.find(x => x.name === 'holdings');
    this.fileInfo = positionInfo;
    routeConfig.settings = {
        fileInfo: positionInfo
    };
    this.router.navigateToRoute('holdings');

And then putting this in the child's activate method:

activate(urlParams, routeMap, navInstr) {
    this.getPositionData(routeMap.settings.fileInfo); 
}

But the settings did not persist after the navigation was executed.



来源:https://stackoverflow.com/questions/42283089/pass-data-to-not-yet-loaded-view-in-aurelia

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!