5.2 KiB
Design Notes
Application State
Apart from loading up the navigation and search data, there is only one significant state in the app, which is the document being viewed.
Other state, such as highlighting the current navigation items and bread crumbs can be derived from that state.
The simplest indication of this state is the current Location#path
, which acts as the single point of truth in the application.
- The document to display is computed from the
path
. - Navigation to display different documents is achieved by updating the
path
. - Whether a navigation item should be highlighted as active is computed from the
path
Navigation between documents can be achieved through changes to the Location
.
Concepts
There are three main concepts in the application: documents, navigation and search.
- The document data includes the contents of the document for rendering.
- The search data includes keywords used for building the search index.
- The navigation data contains information about the visual trees that can be rendered in the app to navigate to the documents.
Documents
Information about documents to be displayed are provided as data that can be fetched from the server.
There are two parts to the data for each document: DocumentContents
.
DocumentContents
contains the actual document that will be rendered. This can be loaded (and then cached) lazily only when the document is to be viewed.
Each document is uniquely identified by the path
that is also used to fetch its DocumentContents
.
The DocumentContents
are stored in JSON files on the server. They are loaded lazily on demand and then cached.
interface DocumentContents {
title: string;
contents: string;
}
type DocumentContentsJSONFile = Array<DocumentContents>;
Navigation
Within an application there can be multiple navigational views for displaying links to documents.
For example, there might be views for the top level Toolbar
and for the more detailed SideBar
navigation.
Each view is represented as a tree of NavigationNode
s. Each NavigationNode
may, optionally, have a url
, a path
and children
.
interface NavigationNode {
title: string;
url?: string;
path?: string;
tooltip?: string;
target?: string;
children?: NavigationNode[];
}
The url
property represents a link to an external url outside of the app.
The path
property identifies the document to display for this navigation url
. It is used both as the URL segment in the browser and in the request to fetch the document contents.
It is not valid to have both url
and path
properties on a single node.
If neither url
nor path
properties are then the node is a pure container, in which case it ought to have
The NavigationNode
data is stored in a JSON file that is fetched from the server at startup
interface NavigationViews {
[name: string]: NavigationNode;
}
The mapping of a URL back to NavigationNode
is computed by inverting all the NavigationNode
structures to produce a NavigationMap
of URL to node.
interface NavigationMap {
[url: string]: NavigationMapItem
}
Each NavigationMapItem
contains information for updating the current navigation views and also caches the DocumentContents
once it has been loaded.
interface NavigationMapItem {
node: NavigationNode;
parents: NavigationNode[];
document?: DocumentContents;
}
Search
The SearchTerms
are stored in a single JSON file that is loaded by the search WebWorker at startup and used to generate the search index.
interface SearchTerms {
url: string;
title: string;
body: string;
}
type SearchTermsJSONFile = Array<SearchTerms>;
LocationService
Since the core Angular Location
service does not expose a subject, nor emits events when the location is changed programmatically,
we have our own LocationService
that does this for us.
export class LocationService {
currentUrl: Observable<string>;
go(url: string): void
}
You can subscribe to the currentUrl
to be updated when location changes occur.
You should navigate to new documents by calling go
.
DocumentService
The DocumentService
is responsible for monitoring the LocationService.currentUrl
and updating the currentDocument
.
When the currentUrl
changes, the DocumentService
will fetch the DocumentContents
from the server if necessary and cache it for the future.
export class DocumentService {
currentDocument: Observable<DocumentContents>;
}
The mapping of URL path to the path to fetch the document contents is simply:
private computePath(url) {
url = url.startsWith('/') ? url : '/' + url;
return 'content/docs' + url + '.json';
}
NavigationService
The NavigationService
is responsible for:
- fetching and exposing the
NavigationViews
for use in displaying navigation UI. - building the
NavigationMap
from theNavigationNode
data. - updating observables for the currently active
NavigationNode
s that can be used to render the navigation UI when theLocationService.currentUrl
changes.
class NavigationService {
navigationViews: Observable<NavigationViews>;
currentNode: Observable<NavigationNode>;
activeNodes: Observable<NavigationNode[]>;
}