@analogjs/router
Version:
Filesystem-based routing for Angular
808 lines (787 loc) • 30.9 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, PLATFORM_ID, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, Injector, makeStateKey, TransferState, input, output, Directive, InjectionToken, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
import { HttpClient, HttpHeaders, ɵHTTP_ROOT_INTERCEPTOR_FNS as _HTTP_ROOT_INTERCEPTOR_FNS, HttpResponse, HttpRequest } from '@angular/common/http';
import { firstValueFrom, map, from, of, throwError, catchError } from 'rxjs';
import { Meta, DomSanitizer } from '@angular/platform-browser';
import { Router, NavigationEnd, ActivatedRoute, provideRouter, ROUTES } from '@angular/router';
import { filter } from 'rxjs/operators';
import { injectAPIPrefix, injectBaseURL, injectRequest, API_PREFIX } from '@analogjs/router/tokens';
import { isPlatformServer } from '@angular/common';
const ROUTE_META_TAGS_KEY = Symbol('@analogjs/router Route Meta Tags Key');
const CHARSET_KEY = 'charset';
const HTTP_EQUIV_KEY = 'httpEquiv';
// httpEquiv selector key needs to be in kebab case format
const HTTP_EQUIV_SELECTOR_KEY = 'http-equiv';
const NAME_KEY = 'name';
const PROPERTY_KEY = 'property';
const CONTENT_KEY = 'content';
function updateMetaTagsOnRouteChange() {
const router = inject(Router);
const metaService = inject(Meta);
router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
const metaTagMap = getMetaTagMap(router.routerState.snapshot.root);
for (const metaTagSelector in metaTagMap) {
const metaTag = metaTagMap[metaTagSelector];
metaService.updateTag(metaTag, metaTagSelector);
}
});
}
function getMetaTagMap(route) {
const metaTagMap = {};
let currentRoute = route;
while (currentRoute) {
const metaTags = currentRoute.data[ROUTE_META_TAGS_KEY] ?? [];
for (const metaTag of metaTags) {
metaTagMap[getMetaTagSelector(metaTag)] = metaTag;
}
currentRoute = currentRoute.firstChild;
}
return metaTagMap;
}
function getMetaTagSelector(metaTag) {
if (metaTag.name) {
return `${NAME_KEY}="${metaTag.name}"`;
}
if (metaTag.property) {
return `${PROPERTY_KEY}="${metaTag.property}"`;
}
if (metaTag.httpEquiv) {
return `${HTTP_EQUIV_SELECTOR_KEY}="${metaTag.httpEquiv}"`;
}
return CHARSET_KEY;
}
const ANALOG_META_KEY = Symbol('@analogjs/router Analog Route Metadata Key');
/**
* This variable reference is replaced with a glob of all route endpoints.
*/
let ANALOG_PAGE_ENDPOINTS = {};
function injectRouteEndpointURL(route) {
const routeConfig = route.routeConfig;
const apiPrefix = injectAPIPrefix();
const baseUrl = injectBaseURL();
const { queryParams, fragment: hash, params, parent } = route;
const segment = parent?.url.map((segment) => segment.path).join('/') || '';
const url = new URL('', import.meta.env['VITE_ANALOG_PUBLIC_BASE_URL'] ||
baseUrl ||
(typeof window !== 'undefined' && window.location.origin
? window.location.origin
: ''));
url.pathname = `${url.pathname.endsWith('/') ? url.pathname : url.pathname + '/'}${apiPrefix}/_analog${routeConfig[ANALOG_META_KEY].endpoint}`;
url.search = `${new URLSearchParams(queryParams).toString()}`;
url.hash = hash ?? '';
Object.keys(params).forEach((param) => {
url.pathname = url.pathname.replace(`[${param}]`, params[param]);
});
url.pathname = url.pathname.replace('**', segment);
return url;
}
function toRouteConfig(routeMeta) {
if (routeMeta && isRedirectRouteMeta(routeMeta)) {
return routeMeta;
}
let { meta, ...routeConfig } = routeMeta ?? {};
if (Array.isArray(meta)) {
routeConfig.data = { ...routeConfig.data, [ROUTE_META_TAGS_KEY]: meta };
}
else if (typeof meta === 'function') {
routeConfig.resolve = {
...routeConfig.resolve,
[ROUTE_META_TAGS_KEY]: meta,
};
}
if (!routeConfig) {
routeConfig = {};
}
routeConfig.runGuardsAndResolvers =
routeConfig.runGuardsAndResolvers ?? 'paramsOrQueryParamsChange';
routeConfig.resolve = {
...routeConfig.resolve,
load: async (route) => {
const routeConfig = route.routeConfig;
if (ANALOG_PAGE_ENDPOINTS[routeConfig[ANALOG_META_KEY].endpointKey]) {
const http = inject(HttpClient);
const url = injectRouteEndpointURL(route);
if (!!import.meta.env['VITE_ANALOG_PUBLIC_BASE_URL'] &&
globalThis.$fetch) {
return globalThis.$fetch(url.pathname);
}
return firstValueFrom(http.get(`${url.href}`));
}
return {};
},
};
return routeConfig;
}
function isRedirectRouteMeta(routeMeta) {
return !!routeMeta.redirectTo;
}
// The Zone is currently enabled by default, so we wouldn't need this check.
// However, leaving this open space will be useful if zone.js becomes optional
// in the future. This means we won't have to modify the current code, and it will
// continue to work seamlessly.
const isNgZoneEnabled = typeof Zone !== 'undefined' && !!Zone.root;
function toMarkdownModule(markdownFileFactory) {
return async () => {
const createLoader = () => Promise.all([import('@analogjs/content'), markdownFileFactory()]);
const [{ parseRawContentFile, MarkdownRouteComponent, ContentRenderer }, markdownFile,] = await (isNgZoneEnabled
? // We are not able to use `runOutsideAngular` because we are not inside
// an injection context to retrieve the `NgZone` instance.
// The `Zone.root.run` is required when the code is running in the
// browser since asynchronous tasks being scheduled in the current context
// are a reason for unnecessary change detection cycles.
Zone.root.run(createLoader)
: createLoader());
const { content, attributes } = parseRawContentFile(markdownFile);
const { title, meta } = attributes;
return {
default: MarkdownRouteComponent,
routeMeta: {
data: { _analogContent: content },
title,
meta,
resolve: {
renderedAnalogContent: async () => {
const contentRenderer = inject(ContentRenderer);
return contentRenderer.render(content);
},
},
},
};
};
}
const ENDPOINT_EXTENSION = '.server.ts';
const APP_DIR = 'src/app';
/**
* This variable reference is replaced with a glob of all page routes.
*/
let ANALOG_ROUTE_FILES = {};
/**
* This variable reference is replaced with a glob of all content routes.
*/
let ANALOG_CONTENT_ROUTE_FILES = {};
/**
* A function used to parse list of files and create configuration of routes.
*
* @param files
* @returns Array of routes
*/
function createRoutes(files, debug = false) {
const filenames = Object.keys(files);
if (filenames.length === 0) {
return [];
}
// map filenames to raw routes and group them by level
const rawRoutesByLevelMap = filenames.reduce((acc, filename) => {
const rawPath = toRawPath(filename);
const rawSegments = rawPath.split('/');
// nesting level starts at 0
// rawPath: /products => level: 0
// rawPath: /products/:id => level: 1
const level = rawSegments.length - 1;
const rawSegment = rawSegments[level];
const ancestorRawSegments = rawSegments.slice(0, level);
return {
...acc,
[level]: {
...acc[level],
[rawPath]: {
filename,
rawSegment,
ancestorRawSegments,
segment: toSegment(rawSegment),
level,
children: [],
},
},
};
}, {});
const allLevels = Object.keys(rawRoutesByLevelMap).map(Number);
const maxLevel = Math.max(...allLevels);
// add each raw route to its parent's children array
for (let level = maxLevel; level > 0; level--) {
const rawRoutesMap = rawRoutesByLevelMap[level];
const rawPaths = Object.keys(rawRoutesMap);
for (const rawPath of rawPaths) {
const rawRoute = rawRoutesMap[rawPath];
const parentRawPath = rawRoute.ancestorRawSegments.join('/');
const parentRawSegmentIndex = rawRoute.ancestorRawSegments.length - 1;
const parentRawSegment = rawRoute.ancestorRawSegments[parentRawSegmentIndex];
// create the parent level and/or raw route if it does not exist
// parent route won't exist for nested routes that don't have a layout route
rawRoutesByLevelMap[level - 1] ||= {};
rawRoutesByLevelMap[level - 1][parentRawPath] ||= {
filename: null,
rawSegment: parentRawSegment,
ancestorRawSegments: rawRoute.ancestorRawSegments.slice(0, parentRawSegmentIndex),
segment: toSegment(parentRawSegment),
level: level - 1,
children: [],
};
rawRoutesByLevelMap[level - 1][parentRawPath].children.push(rawRoute);
}
}
// only take raw routes from the root level
// since they already contain nested routes as their children
const rootRawRoutesMap = rawRoutesByLevelMap[0];
const rawRoutes = Object.keys(rootRawRoutesMap).map((segment) => rootRawRoutesMap[segment]);
sortRawRoutes(rawRoutes);
return toRoutes(rawRoutes, files, debug);
}
function toRawPath(filename) {
return filename
.replace(
// convert to relative path and remove file extension
/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)[\\/]))|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, '')
.replace(/\[\.{3}.+\]/, '**') // [...not-found] => **
.replace(/\[([^\]]+)\]/g, ':$1'); // [id] => :id
}
function toSegment(rawSegment) {
return rawSegment
.replace(/index|\(.*?\)/g, '') // replace named empty segments
.replace(/\.|\/+/g, '/') // replace dots with slashes and remove redundant slashes
.replace(/^\/+|\/+$/g, ''); // remove trailing slashes
}
function toRoutes(rawRoutes, files, debug = false) {
const routes = [];
for (const rawRoute of rawRoutes) {
const children = rawRoute.children.length > 0
? toRoutes(rawRoute.children, files, debug)
: undefined;
let module = undefined;
let analogMeta = undefined;
if (rawRoute.filename) {
const isMarkdownFile = rawRoute.filename.endsWith('.md');
if (!debug) {
module = isMarkdownFile
? toMarkdownModule(files[rawRoute.filename])
: files[rawRoute.filename];
}
const endpointKey = rawRoute.filename.replace(/\.page\.(ts|analog|ag)$/, ENDPOINT_EXTENSION);
// get endpoint path
const rawEndpoint = rawRoute.filename
.replace(/\.page\.(ts|analog|ag)$/, '')
.replace(/\[\.{3}.+\]/, '**') // [...not-found] => **
.replace(/^(.*?)\/pages/, '/pages');
// replace periods, remove (index) paths
const endpoint = (rawEndpoint || '')
.replace(/\./g, '/')
.replace(/\/\((.*?)\)$/, '/-$1-');
analogMeta = {
endpoint,
endpointKey,
};
}
const route = module
? {
path: rawRoute.segment,
loadChildren: () => module().then((m) => {
if (import.meta.env.DEV) {
const hasModuleDefault = !!m.default;
const hasRedirect = !!m.routeMeta?.redirectTo;
if (!hasModuleDefault && !hasRedirect) {
console.warn(`[Analog] Missing default export at ${rawRoute.filename}`);
}
}
return [
{
path: '',
component: m.default,
...toRouteConfig(m.routeMeta),
children,
[ANALOG_META_KEY]: analogMeta,
},
];
}),
}
: {
path: rawRoute.segment,
...(debug
? {
filename: rawRoute.filename ? rawRoute.filename : undefined,
isLayout: children && children.length > 0 ? true : false,
}
: {}),
children,
};
routes.push(route);
}
return routes;
}
function sortRawRoutes(rawRoutes) {
rawRoutes.sort((a, b) => {
let segmentA = deprioritizeSegment(a.segment);
let segmentB = deprioritizeSegment(b.segment);
// prioritize routes with fewer children
if (a.children.length > b.children.length) {
segmentA = `~${segmentA}`;
}
else if (a.children.length < b.children.length) {
segmentB = `~${segmentB}`;
}
return segmentA > segmentB ? 1 : -1;
});
for (const rawRoute of rawRoutes) {
sortRawRoutes(rawRoute.children);
}
}
function deprioritizeSegment(segment) {
// deprioritize param and wildcard segments
return segment.replace(':', '~~').replace('**', '~~~~');
}
const routes = createRoutes({
...ANALOG_ROUTE_FILES,
...ANALOG_CONTENT_ROUTE_FILES,
});
/**
* @deprecated Use `RouteMeta` type instead.
* For more info see: https://github.com/analogjs/analog/issues/223
*
* Defines additional route config metadata. This
* object is merged into the route config with
* the predefined file-based route.
*
* @usageNotes
*
* ```
* import { Component } from '@angular/core';
* import { defineRouteMeta } from '@analogjs/router';
*
* export const routeMeta = defineRouteMeta({
* title: 'Welcome'
* });
*
* @Component({
* template: `Home`,
* standalone: true,
* })
* export default class HomeComponent {}
* ```
*
* @param route
* @returns
*/
const defineRouteMeta = (route) => {
return route;
};
/**
* Returns the instance of Angular Router
*
* @returns The router
*/
const injectRouter = () => {
return inject(Router);
};
/**
* Returns the instance of the Activate Route for the component
*
* @returns The activated route
*/
const injectActivatedRoute = () => {
return inject(ActivatedRoute);
};
function cookieInterceptor(req, next, location = inject(PLATFORM_ID), serverRequest = injectRequest()) {
if (isPlatformServer(location) && req.url.includes('/_analog/')) {
let headers = new HttpHeaders();
const cookies = serverRequest?.headers.cookie;
headers = headers.set('cookie', cookies ?? '');
const cookiedRequest = req.clone({
headers,
});
return next(cookiedRequest);
}
else {
return next(req);
}
}
/**
* Sets up providers for the Angular router, and registers
* file-based routes. Additional features can be provided
* to further configure the behavior of the router.
*
* @param features
* @returns Providers and features to configure the router with routes
*/
function provideFileRouter(...features) {
const extraRoutesFeature = features.filter((feat) => feat.ɵkind >= 100);
const routerFeatures = features.filter((feat) => feat.ɵkind < 100);
return makeEnvironmentProviders([
extraRoutesFeature.map((erf) => erf.ɵproviders),
provideRouter(routes, ...routerFeatures),
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => updateMetaTagsOnRouteChange(),
},
{
provide: _HTTP_ROOT_INTERCEPTOR_FNS,
multi: true,
useValue: cookieInterceptor,
},
{
provide: API_PREFIX,
useFactory() {
return typeof ANALOG_API_PREFIX !== 'undefined'
? ANALOG_API_PREFIX
: 'api';
},
},
]);
}
/**
* Provides extra custom routes in addition to the routes
* discovered from the filesystem-based routing. These routes are
* inserted before the filesystem-based routes, and take priority in
* route matching.
*/
function withExtraRoutes(routes) {
return {
ɵkind: 100,
ɵproviders: [{ provide: ROUTES, useValue: routes, multi: true }],
};
}
function injectLoad(options) {
const injector = options?.injector ?? inject(Injector);
const route = injector.get(ActivatedRoute);
return route.data.pipe(map((data) => data['load']));
}
/**
* Get server load resolver data for the route
*
* @param route Provides the route to get server load resolver
* @returns Returns server load resolver data for the route
*/
async function getLoadResolver(route) {
return route.routeConfig?.resolve?.['load']?.(route);
}
function sortAndConcatParams(params) {
return [...params.keys()]
.sort()
.map((k) => `${k}=${params.getAll(k)}`)
.join('&');
}
function makeCacheKey(request, mappedRequestUrl) {
// make the params encoded same as a url so it's easy to identify
const { params, method, responseType } = request;
const encodedParams = sortAndConcatParams(params);
let serializedBody = request.serializeBody();
if (serializedBody instanceof URLSearchParams) {
serializedBody = sortAndConcatParams(serializedBody);
}
else if (typeof serializedBody !== 'string') {
serializedBody = '';
}
const key = [
method,
responseType,
mappedRequestUrl,
serializedBody,
encodedParams,
].join('|');
const hash = generateHash(key);
return makeStateKey(hash);
}
function generateHash(str) {
let hash = 0;
for (let i = 0, len = str.length; i < len; i++) {
let chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return `${hash}`;
}
/**
* Interceptor that is server-aware when making HttpClient requests.
* Server-side requests use the full URL
* Prerendering uses the internal Nitro $fetch function, along with state transfer
* Client-side requests use the window.location.origin
*
* @param req HttpRequest<unknown>
* @param next HttpHandlerFn
* @returns
*/
function requestContextInterceptor(req, next) {
const apiPrefix = injectAPIPrefix();
const baseUrl = injectBaseURL();
const transferState = inject(TransferState);
// during prerendering with Nitro
if (typeof global !== 'undefined' &&
global.$fetch &&
baseUrl &&
(req.url.startsWith('/') ||
req.url.startsWith(baseUrl) ||
req.url.startsWith(`/${apiPrefix}`))) {
const requestUrl = new URL(req.url, baseUrl);
const cacheKey = makeCacheKey(req, new URL(requestUrl).pathname);
const storeKey = makeStateKey(`analog_${cacheKey}`);
const fetchUrl = requestUrl.pathname;
const responseType = req.responseType === 'arraybuffer' ? 'arrayBuffer' : req.responseType;
return from(global.$fetch
.raw(fetchUrl, {
method: req.method,
body: req.body ? req.body : undefined,
params: requestUrl.searchParams,
responseType,
headers: req.headers.keys().reduce((hdrs, current) => {
return {
...hdrs,
[current]: req.headers.get(current),
};
}, {}),
})
.then((res) => {
const cacheResponse = {
body: res._data,
headers: new HttpHeaders(res.headers),
status: 200,
statusText: 'OK',
url: fetchUrl,
};
const transferResponse = new HttpResponse(cacheResponse);
transferState.set(storeKey, cacheResponse);
return transferResponse;
}));
}
// on the client
if (!import.meta.env.SSR &&
(req.url.startsWith('/') || req.url.includes('/_analog/'))) {
// /_analog/ requests are full URLs
const requestUrl = req.url.includes('/_analog/')
? req.url
: `${window.location.origin}${req.url}`;
const cacheKey = makeCacheKey(req, new URL(requestUrl).pathname);
const storeKey = makeStateKey(`analog_${cacheKey}`);
const cacheRestoreResponse = transferState.get(storeKey, null);
if (cacheRestoreResponse) {
transferState.remove(storeKey);
return of(new HttpResponse(cacheRestoreResponse));
}
return next(req.clone({
url: requestUrl,
}));
}
// on the server
if (baseUrl && (req.url.startsWith('/') || req.url.startsWith(baseUrl))) {
const requestUrl = req.url.startsWith(baseUrl) && !req.url.startsWith('/')
? req.url
: `${baseUrl}${req.url}`;
return next(req.clone({
url: requestUrl,
}));
}
return next(req);
}
class FormAction {
constructor() {
this.action = input('', ...(ngDevMode ? [{ debugName: "action" }] : []));
this.onSuccess = output();
this.onError = output();
this.state = output();
this.router = inject(Router);
this.route = inject(ActivatedRoute);
this.path = this._getPath();
}
submitted($event) {
$event.preventDefault();
this.state.emit('submitting');
const body = new FormData($event.target);
if ($event.target.method.toUpperCase() === 'GET') {
this._handleGet(body, this.router.url);
}
else {
this._handlePost(body, this.path, $event);
}
}
_handleGet(body, path) {
const params = {};
body.forEach((formVal, formKey) => (params[formKey] = formVal));
this.state.emit('navigate');
const url = path.split('?')[0];
this.router.navigate([url], {
queryParams: params,
onSameUrlNavigation: 'reload',
});
}
_handlePost(body, path, $event) {
fetch(path, {
method: $event.target.method,
body,
})
.then((res) => {
if (res.ok) {
if (res.redirected) {
const redirectUrl = new URL(res.url).pathname;
this.state.emit('redirect');
this.router.navigate([redirectUrl]);
}
else if (this._isJSON(res.headers.get('Content-type'))) {
res.json().then((result) => {
this.onSuccess.emit(result);
this.state.emit('success');
});
}
else {
res.text().then((result) => {
this.onSuccess.emit(result);
this.state.emit('success');
});
}
}
else {
if (res.headers.get('X-Analog-Errors')) {
res.json().then((errors) => {
this.onError.emit(errors);
this.state.emit('error');
});
}
else {
this.state.emit('error');
}
}
})
.catch((_) => {
this.state.emit('error');
});
}
_getPath() {
if (this.route) {
return injectRouteEndpointURL(this.route.snapshot).pathname;
}
return `/api/_analog/pages${window.location.pathname}`;
}
_isJSON(contentType) {
const mime = contentType ? contentType.split(';') : [];
const essence = mime[0];
return essence === 'application/json';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: FormAction, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.0", type: FormAction, isStandalone: true, selector: "form[action],form[method]", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSuccess: "onSuccess", onError: "onError", state: "state" }, host: { listeners: { "submit": "submitted($event)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: FormAction, decorators: [{
type: Directive,
args: [{
selector: 'form[action],form[method]',
host: {
'(submit)': `submitted($event)`,
},
standalone: true,
}]
}] });
const DEBUG_ROUTES = new InjectionToken('@analogjs/router debug routes', {
providedIn: 'root',
factory() {
const debugRoutes = createRoutes({
...ANALOG_ROUTE_FILES,
...ANALOG_CONTENT_ROUTE_FILES,
}, true);
return debugRoutes;
},
});
function injectDebugRoutes() {
return inject(DEBUG_ROUTES);
}
/**
* Provides routes that provide additional
* pages for displaying and debugging
* routes.
*/
function withDebugRoutes() {
const routes = [
{
path: '__analog/routes',
loadComponent: () => import('./analogjs-router-debug.page-C7mEWSZu.mjs'),
},
];
return {
ɵkind: 101,
ɵproviders: [{ provide: ROUTES, useValue: routes, multi: true }],
};
}
/**
* @description
* Component that defines the bridge between the client and server-only
* components. The component passes the component ID and props to the server
* and retrieves the rendered HTML and outputs from the server-only component.
*
* Status: experimental
*/
class ServerOnly {
constructor() {
this.component = input.required(...(ngDevMode ? [{ debugName: "component" }] : []));
this.props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : []));
this.outputs = output();
this.http = inject(HttpClient);
this.sanitizer = inject(DomSanitizer);
this.content = signal('', ...(ngDevMode ? [{ debugName: "content" }] : []));
this.route = inject(ActivatedRoute, { optional: true });
this.baseURL = injectBaseURL();
this.transferState = inject(TransferState);
effect(() => {
const routeComponentId = this.route?.snapshot.data['component'];
const props = this.props() || {};
const componentId = routeComponentId || this.component();
const headers = new HttpHeaders(new Headers({
'Content-type': 'application/json',
'X-Analog-Component': componentId,
}));
const componentUrl = this.getComponentUrl(componentId);
const httpRequest = new HttpRequest('POST', componentUrl, props, {
headers,
});
const cacheKey = makeCacheKey(httpRequest, new URL(componentUrl).pathname);
const storeKey = makeStateKey(cacheKey);
const componentState = this.transferState.get(storeKey, null);
if (componentState) {
this.updateContent(componentState);
this.transferState.remove(storeKey);
}
else {
this.http
.request(httpRequest)
.pipe(map((response) => {
if (response instanceof HttpResponse) {
if (import.meta.env.SSR) {
this.transferState.set(storeKey, response.body);
}
return response.body;
}
return throwError(() => ({}));
}), catchError((error) => {
console.log(error);
return of({
html: '',
outputs: {},
});
}))
.subscribe((content) => this.updateContent(content));
}
});
}
updateContent(content) {
this.content.set(this.sanitizer.bypassSecurityTrustHtml(content.html));
this.outputs.emit(content.outputs);
}
getComponentUrl(componentId) {
let baseURL = this.baseURL;
if (!baseURL && typeof window !== 'undefined') {
baseURL = window.location.origin;
}
return `${baseURL}/_analog/components/${componentId}`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ServerOnly, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: ServerOnly, isStandalone: true, selector: "server-only,ServerOnly,Server", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outputs: "outputs" }, ngImport: i0, template: ` <div [innerHTML]="content()"></div> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ServerOnly, decorators: [{
type: Component,
args: [{
selector: 'server-only,ServerOnly,Server',
changeDetection: ChangeDetectionStrategy.OnPush,
template: ` <div [innerHTML]="content()"></div> `,
}]
}], ctorParameters: () => [] });
/**
* Generated bundle index. Do not edit.
*/
export { FormAction, ServerOnly, createRoutes, defineRouteMeta, getLoadResolver, injectActivatedRoute, injectDebugRoutes, injectLoad, injectRouteEndpointURL, injectRouter, provideFileRouter, requestContextInterceptor, routes, withDebugRoutes, withExtraRoutes };
//# sourceMappingURL=analogjs-router.mjs.map