@compodoc/compodoc
Version:
The missing documentation tool for your Angular application
803 lines (662 loc) • 30.2 kB
HTML
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>@compodoc/compodoc documentation</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="../images/favicon.ico">
<link rel="stylesheet" href="../styles/style.css">
<link rel="stylesheet" href="../styles/dark.css">
</head>
<body>
<script>
// Blocking script to avoid flickering dark mode
// Dark mode toggle button
var useDark = window.matchMedia('(prefers-color-scheme: dark)');
var darkModeState = useDark.matches;
var $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
var $darkModeToggles = document.querySelectorAll('.dark-mode-switch');
var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state');
function checkToggle(check) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].checked = check;
}
}
function toggleDarkMode(state) {
if (window.localStorage) {
localStorage.setItem('compodoc_darkmode-state', state);
}
checkToggle(state);
const hasClass = document.body.classList.contains('dark');
if (state) {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.add('dark');
}
if (!hasClass) {
document.body.classList.add('dark');
}
} else {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.remove('dark');
}
if (hasClass) {
document.body.classList.remove('dark');
}
}
}
useDark.addEventListener('change', function (evt) {
toggleDarkMode(evt.matches);
});
if (darkModeStateLocal) {
darkModeState = darkModeStateLocal === 'true';
}
toggleDarkMode(darkModeState);
</script>
<script>
// --- Iframe navigation tracking for Template Playground ---
function sendCurrentUrlToParent() {
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'compodoc-iframe-navigate',
url: window.location.pathname + window.location.hash
}, '*');
}
}
window.addEventListener('hashchange', sendCurrentUrlToParent, false);
window.addEventListener('popstate', sendCurrentUrlToParent, false);
window.addEventListener('DOMContentLoaded', sendCurrentUrlToParent, false);
</script>
<div class="navbar navbar-default navbar-fixed-top d-md-none p-0">
<div class="d-flex">
<a href="../" class="navbar-brand">@compodoc/compodoc documentation</a>
<button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button>
</div>
</div>
<div class="xs-menu menu" id="mobile-menu">
<div id="book-search-input" role="search"><input type="text" placeholder="Saisissez un texte"></div> <compodoc-menu></compodoc-menu>
</div>
<div class="container-fluid main">
<div class="row main">
<div class="d-none d-md-block menu">
<compodoc-menu mode="normal"></compodoc-menu>
</div>
<!-- START CONTENT -->
<div class="content class">
<div class="content-data">
<ol class="breadcrumb">
<li class="breadcrumb-item">Classes</li>
<li class="breadcrumb-item" >NavigationData</li>
</ol>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a href="#info"
class="nav-link"
class="nav-link active"
role="tab" id="info-tab" data-bs-toggle="tab" data-link="info">Info</a>
</li>
<li class="nav-item">
<a href="#source"
class="nav-link"
role="tab" id="source-tab" data-bs-toggle="tab" data-link="source">Source</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active in" id="info">
<p class="comment">
<h3>Fichier</h3>
</p>
<p class="comment">
<code>test/fixtures/sample-files/query-param-group.service.ts</code>
</p>
<section data-compodoc="block-index">
<h3 id="index">Index</h3>
<table class="table table-sm table-bordered index-table">
<tbody>
<tr>
<td class="col-md-4">
<h6><b>Propriétés</b></h6>
</td>
</tr>
<tr>
<td class="col-md-4">
<ul class="index-list">
<li>
<span class="modifier">Public</span>
<a href="#params" >params</a>
</li>
<li>
<span class="modifier">Public</span>
<a href="#synthetic" >synthetic</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</section>
<section data-compodoc="block-constructor">
<h3 id="constructor">Constructeur</h3>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<code>constructor(params: Params, synthetic: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/boolean" target="_blank">boolean</a>)</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Défini dans <a href="" data-line="52" class="link-to-prism">test/fixtures/sample-files/query-param-group.service.ts:52</a></div>
</td>
</tr>
<tr>
<td class="col-md-4">
<div>
<b>Paramètres :</b>
<table class="params">
<thead>
<tr>
<td>Nom</td>
<td>Type</td>
<td>Optionnel</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>
<code>Params</code>
</td>
<td>
Non
</td>
</tr>
<tr>
<td>synthetic</td>
<td>
<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/boolean" target="_blank" >boolean</a></code>
</td>
<td>
Non
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</section>
<section data-compodoc="block-properties">
<h3 id="inputs">
Propriétés
</h3>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="params"></a>
<span class="name">
<span class="modifier">Public</span>
<span ><b>params</b></span>
<a href="#params"><span class="icon ion-ios-link"></span></a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<i>Type : </i> <code>Params</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Défini dans <a href="" data-line="53" class="link-to-prism">test/fixtures/sample-files/query-param-group.service.ts:53</a></div>
</td>
</tr>
</tbody>
</table>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="synthetic"></a>
<span class="name">
<span class="modifier">Public</span>
<span ><b>synthetic</b></span>
<a href="#synthetic"><span class="icon ion-ios-link"></span></a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<i>Type : </i> <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/boolean" target="_blank" >boolean</a></code>
</td>
</tr>
<tr>
<td class="col-md-4">
<i>Valeur par défaut : </i><code>false</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Défini dans <a href="" data-line="53" class="link-to-prism">test/fixtures/sample-files/query-param-group.service.ts:53</a></div>
</td>
</tr>
</tbody>
</table>
</section>
</div>
<div class="tab-pane fade tab-source-code" id="source">
<pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import { Inject, Injectable, isDevMode, OnDestroy, Optional } from '@angular/core';
import { Params } from '@angular/router';
import { EMPTY, from, Observable, Subject } from 'rxjs';
import {
catchError,
concatMap,
debounceTime,
distinctUntilChanged,
filter,
map,
startWith,
switchMap,
takeUntil,
tap
} from 'rxjs/operators';
import { compareParamMaps, filterParamMap, isMissing, isPresent, NOP } from '../util';
import { Unpack } from '../types';
import { QueryParamGroup } from '../model/query-param-group';
import { QueryParam } from '../model/query-param';
import {
NGQP_ROUTER_ADAPTER,
NGQP_ROUTER_OPTIONS,
RouterAdapter,
RouterOptions
} from '../router-adapter/router-adapter.interface';
import { QueryParamAccessor } from './query-param-accessor.interface';
/** @internal */
function isMultiQueryParam<T>(
queryParam: QueryParam<T> | QueryParam<T[]>
): queryParam is QueryParam<T[]> {
return queryParam.multi;
}
/** @internal */
function hasArrayValue<T>(
queryParam: QueryParam<T> | QueryParam<T[]>,
value: T | T[]
): value is T[] {
return isMultiQueryParam(queryParam);
}
/** @internal */
function hasArraySerialization(
queryParam: QueryParam<any>,
values: string | string[] | null
): values is string[] {
return isMultiQueryParam(queryParam);
}
/** @internal */
class NavigationData {
constructor(public params: Params, public synthetic: boolean = false) {}
}
/**
* Service implementing the synchronization logic
*
* This service is the key to the synchronization process by binding a {@link QueryParamGroup}
* to the router.
*
* @internal
*/
@Injectable()
export class QueryParamGroupService implements OnDestroy {
/** The {@link QueryParamGroup} to bind. */
private queryParamGroup: QueryParamGroup;
/** List of {@link QueryParamAccessor} registered to this service. */
private directives = new Map<string, QueryParamAccessor[]>();
/**
* Queue of navigation parameters
*
* A queue is used for navigations as we need to make sure all parameter changes
* are executed in sequence as otherwise navigations might overwrite each other.
*/
private queue$ = new Subject<NavigationData>();
/** @ignore */
private synchronizeRouter$ = new Subject<void>();
/** @ignore */
private destroy$ = new Subject<void>();
constructor(
@Inject(NGQP_ROUTER_ADAPTER) private routerAdapter: RouterAdapter,
@Optional() @Inject(NGQP_ROUTER_OPTIONS) private globalRouterOptions: RouterOptions
) {
this.setupNavigationQueue();
}
/** @ignore */
public ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.synchronizeRouter$.complete();
if (this.queryParamGroup) {
this.queryParamGroup._clearChangeFunctions();
}
}
/**
* Uses the given {@link QueryParamGroup} for synchronization.
*/
public setQueryParamGroup(queryParamGroup: QueryParamGroup): void {
// FIXME: If this is called when we already have a group, we probably need to do
// some cleanup first.
if (this.queryParamGroup) {
throw new Error(
`A QueryParamGroup has already been setup. Changing the group is currently not supported.`
);
}
this.queryParamGroup = queryParamGroup;
this.startSynchronization();
}
/**
* Registers a {@link QueryParamAccessor}.
*/
public registerQueryParamDirective(directive: QueryParamAccessor): void {
// Capture the name here, particularly for the queue below to avoid re-evaluating
// it as it might change over time.
const queryParamName = directive.name;
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
if (!queryParam) {
throw new Error(
`Could not find query param with name ${queryParamName}. Did you forget to add it to your QueryParamGroup?`
);
}
if (!directive.valueAccessor) {
throw new Error(
`No value accessor found for the form control. Please make sure to implement ControlValueAccessor on this component.`
);
}
// Chances are that we read the initial route before a directive has been registered here.
// The value in the model will be correct, but we need to sync it to the view once initially.
directive.valueAccessor.writeValue(queryParam.value);
// Proxy updates from the view to debounce them (if needed).
const debouncedQueue$ = new Subject<any>();
debouncedQueue$
.pipe(
// Do not synchronize while the param is detached from the group
filter(() => !!this.queryParamGroup.get(queryParamName)),
isPresent(queryParam.debounceTime) ? debounceTime(queryParam.debounceTime) : tap(),
map((newValue: any) => this.getParamsForValue(queryParam, newValue)),
takeUntil(this.destroy$)
)
.subscribe(params => this.enqueueNavigation(new NavigationData(params)));
directive.valueAccessor.registerOnChange((newValue: any) => debouncedQueue$.next(newValue));
this.directives.set(queryParamName, [
...(this.directives.get(queryParamName) || []),
directive
]);
}
/**
* Deregisters a {@link QueryParamAccessor} by referencing its name.
*/
public deregisterQueryParamDirective(queryParamName: string): void {
if (!queryParamName) {
return;
}
const directives = this.directives.get(queryParamName);
if (!directives) {
return;
}
directives.forEach(directive => {
directive.valueAccessor.registerOnChange(NOP);
directive.valueAccessor.registerOnTouched(NOP);
});
this.directives.delete(queryParamName);
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
if (queryParam) {
queryParam._clearChangeFunctions();
}
}
private startSynchronization() {
this.setupGroupChangeListener();
this.setupParamChangeListeners();
this.setupRouterListener();
this.watchNewParams();
}
/** Listens for programmatic changes on group level and synchronizes to the router. */
private setupGroupChangeListener(): void {
this.queryParamGroup._registerOnChange((newValue: Record<string, any>) => {
let params: Params = {};
Object.keys(newValue).forEach(queryParamName => {
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
if (isMissing(queryParam)) {
return;
}
params = {
...params,
...this.getParamsForValue(queryParam, newValue[queryParamName])
};
});
this.enqueueNavigation(new NavigationData(params, true));
});
}
/** Listens for programmatic changes on parameter level and synchronizes to the router. */
private setupParamChangeListeners(): void {
Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName =>
this.setupParamChangeListener(queryParamName)
);
}
private setupParamChangeListener(queryParamName: string): void {
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
if (!queryParam) {
throw new Error(`No param in group found for name ${queryParamName}`);
}
queryParam._registerOnChange((newValue: any) =>
this.enqueueNavigation(
new NavigationData(this.getParamsForValue(queryParam, newValue), true)
)
);
}
/** Listens for changes in the router and synchronizes to the model. */
private setupRouterListener(): void {
this.synchronizeRouter$
.pipe(
startWith(undefined),
switchMap(() =>
this.routerAdapter.queryParamMap.pipe(
// We want to ignore changes to query parameters which aren't related to this
// particular group; however, we do need to react if one of our parameters has
// vanished when it was set before.
distinctUntilChanged((previousMap, currentMap) => {
const keys = Object.values(this.queryParamGroup.queryParams).map(
queryParam => queryParam.urlParam
);
// It is important that we filter the maps only here so that both are filtered
// with the same set of keys; otherwise, e.g. removing a parameter from the group
// would interfere.
return compareParamMaps(
filterParamMap(previousMap, keys),
filterParamMap(currentMap, keys)
);
})
)
),
takeUntil(this.destroy$)
)
.subscribe(queryParamMap => {
const synthetic = this.isSyntheticNavigation();
const groupValue: Record<string, any> = {};
Object.keys(this.queryParamGroup.queryParams).forEach(queryParamName => {
const queryParam: QueryParam<any> = this.queryParamGroup.get(queryParamName);
const newValue = queryParam.multi
? this.deserialize(queryParam, queryParamMap.getAll(queryParam.urlParam))
: this.deserialize(queryParam, queryParamMap.get(queryParam.urlParam));
const directives = this.directives.get(queryParamName);
if (directives) {
directives.forEach(directive =>
directive.valueAccessor.writeValue(newValue)
);
}
groupValue[queryParamName] = newValue;
});
this.queryParamGroup.setValue(groupValue, {
emitEvent: !synthetic,
emitModelToViewChange: false
});
});
}
/** Listens for newly added parameters and starts synchronization for them. */
private watchNewParams(): void {
this.queryParamGroup.queryParamAdded$
.pipe(takeUntil(this.destroy$))
.subscribe(queryParamName => {
this.setupParamChangeListener(queryParamName);
this.synchronizeRouter$.next();
});
}
/** Returns true if the current navigation is synthetic. */
private isSyntheticNavigation(): boolean {
const navigation = this.routerAdapter.getCurrentNavigation();
if (!navigation || navigation.trigger !== 'imperative') {
// When using the back / forward buttons, the state is passed along with it, even though
// for us it's now a navigation initiated by the user. Therefore, a navigation can only
// be synthetic if it has been triggered imperatively.
// See https://github.com/angular/angular/issues/28108.
return false;
}
return navigation.extras && navigation.extras.state && navigation.extras.state['synthetic'];
}
/** Subscribes to the parameter queue and executes navigations in sequence. */
private setupNavigationQueue() {
this.queue$
.pipe(
takeUntil(this.destroy$),
concatMap(data => this.navigateSafely(data))
)
.subscribe();
}
private navigateSafely(data: NavigationData): Observable<any> {
return from(
this.routerAdapter.navigate(data.params, {
...this.routerOptions,
state: { synthetic: data.synthetic }
})
).pipe(
catchError((err: any) => {
if (isDevMode()) {
console.error(`There was an error while navigating`, err);
}
return EMPTY;
})
);
}
/** Sends a change of parameters to the queue. */
private enqueueNavigation(data: NavigationData): void {
this.queue$.next(data);
}
/**
* Returns the full set of parameters given a value for a parameter model.
*
* This consists mainly of properly serializing the model value and ensuring to take
* side effect changes into account that may have been configured.
*/
private getParamsForValue<T>(queryParam: QueryParam<any>, value: T | undefined | null): Params {
const newValue = this.serialize(queryParam, value);
const combinedParams: Params = isMissing(queryParam.combineWith)
? {}
: queryParam.combineWith(value);
// Note that we list the side-effect parameters first so that our actual parameter can't be
// overridden by it.
return {
...(combinedParams || {}),
[queryParam.urlParam]: newValue
};
}
private serialize<T>(queryParam: QueryParam<any>, value: T): string | string[] {
if (hasArrayValue(queryParam, value)) {
return (value || []).map(queryParam.serialize);
} else {
return queryParam.serialize(value);
}
}
private deserialize<T>(
queryParam: QueryParam<T>,
values: string | string[]
): Unpack<T> | Unpack<T>[] {
if (hasArraySerialization(queryParam, values)) {
return values.map(queryParam.deserialize);
} else {
return queryParam.deserialize(values);
}
}
/**
* Returns the current set of options to pass to the router.
*
* This merges the global configuration with the group specific configuration.
*/
private get routerOptions(): RouterOptions {
const groupOptions = this.queryParamGroup ? this.queryParamGroup.routerOptions : {};
return {
...(this.globalRouterOptions || {}),
...groupOptions
};
}
}
</code></pre>
</div>
</div>
</div><div class="search-results">
<div class="has-results">
<h1 class="search-results-title"><span class='search-results-count'></span> résultats matchant "<span class='search-query'></span>"</h1>
<ul class="search-results-list"></ul>
</div>
<div class="no-results">
<h1 class="search-results-title">Aucun résultat matchant "<span class='search-query'></span>"</h1>
</div>
</div>
</div>
<!-- END CONTENT -->
</div>
</div>
<label class="dark-mode-switch">
<input type="checkbox">
<span class="slider">
<svg class="slider-icon" viewBox="0 0 24 24" fill="none" height="20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</span>
</label>
<script>
var COMPODOC_CURRENT_PAGE_DEPTH = 1;
var COMPODOC_CURRENT_PAGE_CONTEXT = 'class';
var COMPODOC_CURRENT_PAGE_URL = 'NavigationData.html';
var MAX_SEARCH_RESULTS = 15;
</script>
<script>
$darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
checkToggle(darkModeState);
if ($darkModeToggleSwitchers.length > 0) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].addEventListener('change', function (event) {
darkModeState = !darkModeState;
toggleDarkMode(darkModeState);
});
}
}
</script>
<script src="../js/libs/custom-elements.min.js"></script>
<script src="../js/libs/lit-html.js"></script>
<script src="../js/menu-wc.js" defer></script>
<script nomodule src="../js/menu-wc_es5.js" defer></script>
<script src="../js/libs/bootstrap-native.js"></script>
<script src="../js/libs/es6-shim.min.js"></script>
<script src="../js/libs/EventDispatcher.js"></script>
<script src="../js/libs/promise.min.js"></script>
<script src="../js/compodoc.js"></script>
<script src="../js/tabs.js"></script>
<script src="../js/menu.js"></script>
<script src="../js/libs/clipboard.min.js"></script>
<script src="../js/libs/prism.js"></script>
<script src="../js/sourceCode.js"></script>
<script src="../js/search/search.js"></script>
<script src="../js/search/lunr.min.js"></script>
<script src="../js/search/search-lunr.js"></script>
<script src="../js/search/search_index.js"></script>
<script src="../js/lazy-load-graphs.js"></script>
</body>
</html>