{"id":6848,"date":"2023-01-16T13:15:12","date_gmt":"2023-01-16T12:15:12","guid":{"rendered":"https:\/\/www.angulararchitects.io\/?p=6848"},"modified":"2023-01-16T13:15:12","modified_gmt":"2023-01-16T12:15:12","slug":"patterns-for-custom-standalone-apis-in-angular","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/","title":{"rendered":"Patterns for Custom Standalone APIs in Angular"},"content":{"rendered":"<p>Together with Standalone Components, the Angular team introduced Standalone APIs. They allow for setting up libraries in a more lightweight way. Examples of libraries currently providing Standalone APIs are the <code>HttpClient<\/code> and the <code>Router<\/code>. Also, NGRX is an early adopter of this idea.<\/p>\n<p>In this article, I present several patterns for writing custom Standalone APIs inferred from the before mentioned libraries. For each pattern, the following aspects are discussed: intentions behind the pattern, description, example implementation, examples for occurrences in the mentioned libraries, and variations for implementation details.<\/p>\n<p>Most of these patterns are especially interesting for library authors. They have the potential to improve the DX for the library's consumers. On the other side, most of them might be overkill for applications. <\/p>\n<blockquote><p>\nBig thanks to Angular's <a href=\"https:\/\/twitter.com\/synalx\">Alex Rickabaugh<\/a> for proofreading and providing feedback!\n<\/p><\/blockquote>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/standalone-example-cli.git\">Source code used in examples<\/a><\/p>\n<h2>Example<\/h2>\n<p>For presenting the inferred patterns, a simple logger library is used. This library is as simple as possible but as complex as needed to demonstrate the implementation of the patterns:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/logger.png\" style=\"max-width:300px\"><\/p>\n<p>Each log message has a <code>LogLevel<\/code>, defined by an enum:<\/p>\n<pre><code class=\"language-typescript\">export enum LogLevel {\n  DEBUG = 0,\n  INFO = 1,\n  ERROR = 2,\n}<\/code><\/pre>\n<p>For the sake of simplicity, we restrict our Logger library to just three log levels. <\/p>\n<p>An abstract <code>LoggerConfig<\/code> defines the possible configuration options:<\/p>\n<pre><code class=\"language-typescript\">export abstract class LoggerConfig {\n  abstract level: LogLevel;\n  abstract formatter: Type&lt;LogFormatter&gt;;\n  abstract appenders: Type&lt;LogAppender&gt;[];\n}<\/code><\/pre>\n<p>It's an abstract class on purpose, as interfaces cannot be used as tokens for DI. A constant of this class type defines the default values for the configuration options:<\/p>\n<pre><code class=\"language-typescript\">export const defaultConfig: LoggerConfig = {\n  level: LogLevel.DEBUG,\n  formatter: DefaultLogFormatter,\n  appenders: [DefaultLogAppender],\n};<\/code><\/pre>\n<p>The <code>LogFormatter<\/code> is used for formatting log messages before they are published via a <code>LogAppender<\/code>:<\/p>\n<pre><code class=\"language-typescript\">export abstract class LogFormatter {\n  abstract format(level: LogLevel, category: string, msg: string): string;\n}<\/code><\/pre>\n<p>Like the <code>LoggerConfiguration<\/code>, the <code>LogFormatter<\/code> is an abstract class used as a token. The consumer of the logger lib can adjust the formatting by providing its own implementation. As an alternative, they can go with a default implementation provided by the lib:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class DefaultLogFormatter implements LogFormatter {\n  format(level: LogLevel, category: string, msg: string): string {\n    const levelString = LogLevel[level].padEnd(5);\n    return <code>[${levelString}] ${category.toUpperCase()} ${msg}<\/code>;\n  }\n}<\/code><\/pre>\n<p>The <code>LogAppender<\/code> is another exchangeable concept responsible for appending the log message to a log:<\/p>\n<pre><code class=\"language-typescript\">export abstract class LogAppender {\n  abstract append(level: LogLevel, category: string, msg: string): void;\n}<\/code><\/pre>\n<p>The default implementation writes the message to the console:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class DefaultLogAppender implements LogAppender {\n  append(level: LogLevel, category: string, msg: string): void {\n    console.log(category + &#039; &#039; + msg);\n  }\n}<\/code><\/pre>\n<p>While there can only be one <code>LogFormatter<\/code>, the library supports several <code>LogAppenders<\/code>. For instance, a first <code>LogAppender<\/code> could write the message to the console while a second one could also send it to the server. <\/p>\n<p>To make this possible, the individual <code>LogAppender<\/code>s are registered via multi providers. Hence, the Injector returns all of them within an array. As an array cannot be used as a DI token, the example uses an <code>InjectionToken<\/code> instead:<\/p>\n<pre><code class=\"language-typescript\">export const LOG_APPENDERS = new InjectionToken&lt;LogAppender[]&gt;(&quot;LOG_APPENDERS&quot;);<\/code><\/pre>\n<p>The <code>LoggserService<\/code> itself receives the <code>LoggerConfig<\/code>, the <code>LogFormatter<\/code>, and an array with <code>LogAppenders<\/code> via DI and allows to log messages for several <code>LogLevels<\/code>:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class LoggerService {\n  private config = inject(LoggerConfig);\n  private formatter = inject(LogFormatter);\n  private appenders = inject(LOG_APPENDERS);\n\n  log(level: LogLevel, category: string, msg: string): void {\n    if (level &lt; this.config.level) {\n      return;\n    }\n    const formatted = this.formatter.format(level, category, msg);\n    for (const a of this.appenders) {\n      a.append(level, category, formatted);\n    }\n  }\n\n  error(category: string, msg: string): void {\n    this.log(LogLevel.ERROR, category, msg);\n  }\n\n  info(category: string, msg: string): void {\n    this.log(LogLevel.INFO, category, msg);\n  }\n\n  debug(category: string, msg: string): void {\n    this.log(LogLevel.DEBUG, category, msg);\n  }\n}<\/code><\/pre>\n<h2>The Golden Rule<\/h2>\n<p>Before I start with presenting the inferred patterns, I want to stress out what I call the golden rule for providing services: <\/p>\n<blockquote><p>\nWhenever possible, use <code>@Injectable({providedIn: &#039;root&#039;})<\/code>!\n<\/p><\/blockquote>\n<p>Especially in application code but in several situations in libraries, this is what you want to have: It's easy, tree-shakable, and even works with lazy loading. The latter aspect is less a merit of Angular than the underlying bundler: Everything that is just needed in a lazy bundle is put there. <\/p>\n<h2>Pattern: Provider Factory<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Providing services for a reusable lib<\/li>\n<li>Configuring a reusable lib<\/li>\n<li>Exchanging defined implementation details<\/li>\n<\/ul>\n<h3>Description<\/h3>\n<p>A Provider Factory is a function returning an array with providers for a given library. This Array is cross-casted into Angular's <code>EnvironmentProviders<\/code> type to make sure the providers can only be used in an environment scope -- first and foremost, the root scope and scopes introduced with lazy routing configurations.<\/p>\n<p>Angular and NGRX place such functions in files called <code>provider.ts<\/code>.<\/p>\n<h3>Example<\/h3>\n<p>The following Provider Function <code>provideLogger<\/code> takes a partial <code>LoggerConfiguration<\/code> and uses it to create some providers:<\/p>\n<pre><code class=\"language-typescript\">export function provideLogger(\n  config: Partial&lt;LoggerConfig&gt;\n): EnvironmentProviders {\n  \/\/ using default values for missing properties\n  const merged = { ...defaultConfig, ...config };\n\n  return makeEnvironmentProviders([\n    {\n      provide: LoggerConfig,\n      useValue: merged,\n    },\n    {\n      provide: LogFormatter,\n      useClass: merged.formatter,\n    },\n    merged.appenders.map((a) =&gt; ({\n      provide: LOG_APPENDERS,\n      useClass: a,\n      multi: true,\n    })),\n  ]);\n}<\/code><\/pre>\n<p>Missing configuration values are taken from the default configuration. Angular's <code>makeEnvironmentProviders<\/code> wraps the <code>Provider<\/code> array in an instance of <code>EnvironmentProviders<\/code>.<\/p>\n<p>This function allows the consuming application to setup the logger during bootstrapping like other libraries, e. g. the <code>HttpClient<\/code> or the <code>Router<\/code>:<\/p>\n<pre><code class=\"language-typescript\">bootstrapApplication(AppComponent, {\n  providers: [\n\n    provideHttpClient(),\n\n    provideRouter(APP_ROUTES),\n\n    [...]\n\n    \/\/ Setting up the Logger:\n    provideLogger(loggerConfig),\n  ]\n}<\/code><\/pre>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li>This is a usual pattern used in all examined libraries<\/li>\n<li>The Provider Factories for the <code>Router<\/code> and <code>HttpClient<\/code> have a second optional parameter that takes additional features (see Pattern <em>Feature<\/em>, below).<\/li>\n<li>Instead of passing in the concrete service implementation, e. g. LogFormatter, NGRX allows taking either a token or the concrete object for reducers.<\/li>\n<li>The <code>HttpClient<\/code> takes an array with functional interceptors via a <code>with<\/code> function (see Pattern <em>Feature<\/em>, below). These functions are also registered as services.<\/li>\n<\/ul>\n<h2>Pattern: Feature<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Activating and configuring optional features<\/li>\n<li>Making these features tree-shakable<\/li>\n<li>Providing the underlying services via the current environment scope<\/li>\n<\/ul>\n<h3>Description<\/h3>\n<p>The Provider Factory takes an optional array with a feature object. Each feature object has an identifier called <code>kind<\/code> and a <code>providers<\/code> array. The <code>kind<\/code> property allows for validating the combination of passed features. For instance, there might be mutually exclusive features like configuring XSRF token handling and disabling XSRF token handling for the <code>HttpClient<\/code>.<\/p>\n<h3>Example<\/h3>\n<p>Our example uses a color feature that allows for displaying messages of different <code>LoggerLevel<\/code>s in different colors:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/feature.png\" style=\"max-width:300px\"><\/p>\n<p>For categorizing features, an enum is used:<\/p>\n<pre><code class=\"language-typescript\">export enum LoggerFeatureKind {\n    COLOR,\n    OTHER_FEATURE,\n    ADDITIONAL_FEATURE\n}<\/code><\/pre>\n<p>Each feature is represented by an object of <code>LoggerFeature<\/code>:<\/p>\n<pre><code class=\"language-typescript\">export interface LoggerFeature {\n  kind: LoggerFeatureKind;\n  providers: Provider[];\n}<\/code><\/pre>\n<p>For providing the color feature, a factory function following the naming pattern with<em>Feature<\/em> is introduced:<\/p>\n<pre><code class=\"language-typescript\">export function withColor(config?: Partial&lt;ColorConfig&gt;): LoggerFeature {\n  const internal = { ...defaultColorConfig, ...config };\n\n  return {\n    kind: LoggerFeatureKind.COLOR,\n    providers: [\n      {\n        provide: ColorConfig,\n        useValue: internal,\n      },\n      {\n        provide: ColorService,\n        useClass: DefaultColorService,\n      },\n    ],\n  };\n}<\/code><\/pre>\n<p>The Provider Factory takes several features via an optional second parameter defined as a rest array:<\/p>\n<pre><code class=\"language-typescript\">export function provideLogger(\n  config: Partial&lt;LoggerConfig&gt;,\n  ...features: LoggerFeature[]\n): EnvironmentProviders {\n  const merged = { ...defaultConfig, ...config };\n\n  \/\/ Inspecting passed features\n  const colorFeatures =\n    features?.filter((f) =&gt; f.kind === LoggerFeatureKind.COLOR)?.length ?? 0;\n\n  \/\/ Validating passed features\n  if (colorFeatures &gt; 1) {\n    throw new Error(&quot;Only one color feature allowed for logger!&quot;);\n  }\n\n  return makeEnvironmentProviders([\n    {\n      provide: LoggerConfig,\n      useValue: merged,\n    },\n    {\n      provide: LogFormatter,\n      useClass: merged.formatter,\n    },\n    merged.appenders.map((a) =&gt; ({\n      provide: LOG_APPENDERS,\n      useClass: a,\n      multi: true,\n    })),\n\n    \/\/ Providing services for the features\n    features?.map((f) =&gt; f.providers),\n  ]);\n}<\/code><\/pre>\n<p>The <code>kind<\/code> property of the feature is used to examine and validate the passed features. If everything is fine, the providers found in the feature are put into the returned <code>EnvironmentProviders<\/code> object.<\/p>\n<p>The <code>DefaultLogAppender<\/code> gets hold of the <code>ColorService<\/code> provided by the color feature via dependency injection:<\/p>\n<pre><code class=\"language-typescript\">export class DefaultLogAppender implements LogAppender {\n  colorService = inject(ColorService, { optional: true });\n\n  append(level: LogLevel, category: string, msg: string): void {\n    if (this.colorService) {\n      msg = this.colorService.apply(level, msg);\n    }\n    console.log(msg);\n  }\n}<\/code><\/pre>\n<p>As features are optional, the <code>DefaultLogAppender<\/code> passes <code>optional: true<\/code> to <code>inject<\/code>. Otherwise, we would get an exception if the feature is not applied. Also, the <code>DefaultLogAppender<\/code> needs to check for <code>null<\/code> values.<\/p>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li>The <code>Router<\/code> uses it, e. g. for configuring preloading or for activating debug tracing.<\/li>\n<li>The <code>HttpClient<\/code> uses it, e. g. for providing interceptors, configuring JSONP, and configuring\/ disabling the XSRF token handling<\/li>\n<li>Both, the <code>Router<\/code> and the <code>HttpClient<\/code>, combine the possible features to a union type (e.g. <code>export type AllowedFeatures = ThisFeature | ThatFeature<\/code>). This helps IDEs to propose built-in features.<\/li>\n<li>Some implementations inject the current <code>Injector<\/code> and use it to find out which features have been configured. This is an imperative alternative to using <code>optional: true<\/code>.<\/li>\n<li>Angular's feature implementations prefix the properties <code>kind<\/code> and <code>providers<\/code> with <code>\u0275<\/code> and hence declare them as internal properties.<\/li>\n<\/ul>\n<h2>Pattern: Configuration Provider Factory<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Configuring existing services<\/li>\n<li>Providing additional services and registering them with existing services<\/li>\n<li>Extending the behavior of a service from within a nested environment scope<\/li>\n<\/ul>\n<h3>Description<\/h3>\n<p>Configuration Provider Factories extend the behavior of an existing service. They may provide additional services and use an <code>ENVIRONMENT_INITIALIZER<\/code> to get hold of instances of both the provided services as well as the existing services to extend.<\/p>\n<h3>Example<\/h3>\n<p>Let's assume an extended version of our <code>LoggerService<\/code> that allows for defining an additional <code>LogAppender<\/code> for each log category:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class LoggerService {\n\n    private appenders = inject(LOG_APPENDERS);\n    private formatter = inject(LogFormatter);\n    private config = inject(LoggerConfig);\n    [...]\n\n    \/\/ Additional LogAppender per log category\n    readonly categories: Record&lt;string, LogAppender&gt; = {};\n\n    log(level: LogLevel, category: string, msg: string): void {\n\n        if (level &lt; this.config.level) {\n            return;\n        }\n\n        const formatted = this.formatter.format(level, category, msg);\n\n        \/\/ Lookup appender for this very category and use\n        \/\/ it, if there is one:\n        const catAppender = this.categories[category];\n\n        if (catAppender) {\n            catAppender.append(level, category, formatted);\n        }\n\n        \/\/ Also, use default appenders:\n        for (const a of this.appenders) {\n            a.append(level, category, formatted);\n        }\n\n    }\n\n    [...]\n}<\/code><\/pre>\n<p>To configurate a <code>LogAppender<\/code> for a category, we can introduce another Provider Factory:<\/p>\n<pre><code class=\"language-typescript\">export function provideCategory(\n  category: string,\n  appender: Type&lt;LogAppender&gt;\n): EnvironmentProviders {\n  \/\/ Internal\/ Local token for registering the service\n  \/\/ and retrieving the resolved service instance\n  \/\/ immediately after.\n  const appenderToken = new InjectionToken&lt;LogAppender&gt;(&quot;APPENDER_&quot; + category);\n\n  return makeEnvironmentProviders([\n    {\n      provide: appenderToken,\n      useClass: appender,\n    },\n    {\n      provide: ENVIRONMENT_INITIALIZER,\n      multi: true,\n      useValue: () =&gt; {\n        const appender = inject(appenderToken);\n        const logger = inject(LoggerService);\n\n        logger.categories[category] = appender;\n      },\n    },\n  ]);\n}<\/code><\/pre>\n<p>This factory creates a provider for the <code>LogAppender<\/code> class. However, we don't need the class but rather an instance of it. Also, we need the <code>Injector<\/code> to resolve this instance's dependencies. Both happen when retrieving a <code>LogAppender<\/code> via inject. <\/p>\n<p>Precisely this is done by the <code>ENVIRONMENT_INITIALIZER<\/code>, which is multi provider bound to the token <code>ENVIRONMENT_INITIALIZER<\/code> and pointing to a function. It gets the <code>LogAppender<\/code> injected but also the <code>LoggerService<\/code>. Then, the <code>LogAppender<\/code> is registered with the logger. <\/p>\n<p>This allows for extending the existing <code>LoggerService<\/code> that can even come from a parent scope. For instance, the following example assumes the <code>LoggerService<\/code> in the root scope while the additional log category is setup in the scope of a lazy route:<\/p>\n<pre><code class=\"language-typescript\">export const FLIGHT_BOOKING_ROUTES: Routes = [\n  {\n    path: &#039;&#039;,\n    component: FlightBookingComponent,\n\n    \/\/ Providers for this route and child routes\n    \/\/ Using the providers array sets up a new\n    \/\/ environment injector for this part of the\n    \/\/ application.\n    providers: [\n      \/\/ Setting up an NGRX feature slice\n      provideState(bookingFeature),\n      provideEffects([BookingEffects]),\n\n      \/\/ Provide LogAppender for logger category\n      provideCategory(&#039;booking&#039;, DefaultLogAppender),\n    ],\n    children: [\n      {\n        path: &#039;flight-search&#039;,\n        component: FlightSearchComponent,\n      },\n      [...]\n    ],\n  },\n];<\/code><\/pre>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li><code>@ngrx\/store<\/code> uses this pattern to register feature slices<\/li>\n<li><code>@ngrx\/effects<\/code> uses this pattern, to wire-up effects provided by a feature<\/li>\n<li>The feature <code>withDebugTracing<\/code> uses this pattern to subscribe to the <code>Router<\/code>'s <code>events<\/code> Observable.<\/li>\n<\/ul>\n<h2>Pattern: NgModule Bridge<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Not breaking existing code using <code>NgModules<\/code> when switching to Standalone APIs.<\/li>\n<li>Allowing such application parts to set up <code>EnvironmentProviders<\/code> that come from a Provider Factory. <\/li>\n<\/ul>\n<p>Remarks: For new code, this pattern seems to be overkill, because the Provider Factory can be directly called for the consuming (legacy) NgModules. <\/p>\n<h3>Description<\/h3>\n<p>The NgModule Bridge is a NgModule deriving (some of) its providers via a Provider Factory (see pattern <em>Provider Factory<\/em>). To give the caller more control over the provided services, static methods like <code>forRoot<\/code> can be used. These methods can take a configuration object.<\/p>\n<h3>Example<\/h3>\n<p>The following <code>NgModules<\/code> allows for setting up the Logger in a traditional way:<\/p>\n<pre><code class=\"language-typescript\">@NgModule({\n  imports: [\/* your imports here *\/],\n  exports: [\/* your exports here *\/],\n  declarations: [\/* your delarations here *\/],\n  providers: [\/* providers, you _always_ want to get, here *\/],\n})\nexport class LoggerModule {\n  static forRoot(config = defaultConfig): ModuleWithProviders&lt;LoggerModule&gt; {\n    return {\n      ngModule: LoggerModule,\n      providers: [\n        provideLogger(config)\n      ],\n    };\n  }\n\n  static forCategory(\n    category: string,\n    appender: Type&lt;LogAppender&gt;\n  ): ModuleWithProviders&lt;LoggerModule&gt; {\n    return {\n      ngModule: LoggerModule,\n      providers: [\n        provideCategory(category, appender)\n      ],\n    };\n  }\n}<\/code><\/pre>\n<p>To avoid reimplementing the Provider Factories, the Module's methods delegate to them. As using such methods is usual when working with NgModules, consumers can leverage existing knowledge and conventions.<\/p>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li>All the examined libraries use this pattern to stay backwards compatible<\/li>\n<\/ul>\n<h2>Pattern: Service Chain<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Making a service delegating to another instance of itself in a parent scope.<\/li>\n<\/ul>\n<h3>Description<\/h3>\n<p>When the same service is placed in several nested environment injectors, we normally only get the service instance of the current scope. Hence, a call to the service in a nested scope is not respected in the parent scope. To work around this, a service can look up an instance of itself in the parent scope and delegate to it.<\/p>\n<h3>Example<\/h3>\n<p>Let's assume we provide the logger library again for a lazy route:<\/p>\n<pre><code class=\"language-typescript\">export const FLIGHT_BOOKING_ROUTES: Routes = [\n  {\n    path: &#039;&#039;,\n    component: FlightBookingComponent,\n    canActivate: [() =&gt; inject(AuthService).isAuthenticated()],\n    providers: [\n      \/\/ NGRX\n      provideState(bookingFeature),\n      provideEffects([BookingEffects]),\n\n      \/\/ Providing **another** logger for this part of the app:\n      provideLogger(\n        {\n          level: LogLevel.DEBUG,\n          chaining: true,\n          appenders: [DefaultLogAppender],\n        },\n        withColor({\n          debug: 42,\n          error: 43,\n          info: 46,\n        })\n      ),\n\n    ],\n    children: [\n      {\n        path: &#039;flight-search&#039;,\n        component: FlightSearchComponent,\n      },\n      [...]\n    ],\n  },\n];<\/code><\/pre>\n<p>This sets up <strong>another<\/strong> set of the Logger's services in the environment injector of this lazy route and its children. These services are shadowing their counterparts in the root scope. Hence, when a component in the lazy scope calls the <code>LoggerService<\/code>, the services in the root scope are not triggered.<\/p>\n<p>To prevent this, we can get the <code>LoggerService<\/code> from the parent scope. More precisely, it's not <em>the<\/em> parent scope but the &quot;nearest ancestor scope&quot; providing a <code>LoggerService<\/code>. After that, the service can delegate to its parent. This way, the services are chained:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class LoggerService {\n  private appenders = inject(LOG_APPENDERS);\n  private formatter = inject(LogFormatter);\n  private config = inject(LoggerConfig);\n\n  private parentLogger = inject(LoggerService, {\n    optional: true,\n    skipSelf: true,\n  });\n  [...]\n\n  log(level: LogLevel, category: string, msg: string): void {\n\n    \/\/ 1. Do own stuff here\n    [...]\n\n    \/\/ 2. Delegate to parent\n    if (this.config.chaining &amp;&amp; this.parentLogger) {\n        this.parentLogger.log(level, category, msg);\n    }\n  }\n  [...]\n}<\/code><\/pre>\n<p>When using inject to get hold of the parent's <code>LoggerService<\/code>, we need to pass the <code>optional: true<\/code> to avoid an exception if there is no ancestor scope with a <code>LoggerService<\/code>. Passing <code>skipSelf: true<\/code> makes sure, only ancestor scopes are searched. Otherwise, Angular would start with the current scope and retrieve the calling service itself.<\/p>\n<p>Also, the example shown here allows activating\/deactivating this behavior via a new <code>chaining<\/code> flag in the <code>LoggerConfiguration<\/code>.<\/p>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li>The <code>HttpClient<\/code> uses this pattern to also trigger <code>HttpInterceptors<\/code> in parent scopes. More details on <a href=\"https:\/\/www.angulararchitects.io\/aktuelles\/the-refurbished-httpclient-in-angular-15-standalone-apis-and-functional-interceptors\/\">chaining HttpInterceptors<\/a> can be found <a href=\"https:\/\/www.angulararchitects.io\/aktuelles\/the-refurbished-httpclient-in-angular-15-standalone-apis-and-functional-interceptors\/\">here<\/a>. Here, the chaining behavior can be activated via a separate feature. Technically, this feature registers another interceptor delegating to services in the parent scope.<\/li>\n<\/ul>\n<h2>Pattern: Functional Service<\/h2>\n<h3>Intentions<\/h3>\n<ul>\n<li>Making the usage of libraries more lightweight by using functions as services<\/li>\n<li>Reducing indirections by going with ad-hoc functions<\/li>\n<\/ul>\n<h3>Description<\/h3>\n<p>Instead of forcing the consumer to implement a class-based service following a given interface, a library also accepts functions. Internally, they can be registered as a service using <code>useValue<\/code>.<\/p>\n<h3>Example<\/h3>\n<p>In this example, the consumer can directly pass a function acting as a <code>LogFormatter<\/code> to <code>provideLogger<\/code>: <\/p>\n<pre><code class=\"language-typescript\">bootstrapApplication(AppComponent, {\n  providers: [\n    provideLogger(\n      {\n        level: LogLevel.DEBUG,\n        appenders: [DefaultLogAppender],\n\n        \/\/ Functional CSV-Formatter\n        formatter: (level, cat, msg) =&gt; [level, cat, msg].join(&quot;;&quot;),\n      },\n      withColor({\n        debug: 3,\n      })\n    ),\n  ],\n});<\/code><\/pre>\n<p>To allow for this, the Logger uses a <code>LogFormatFn<\/code> type defining the function's signature:<\/p>\n<pre><code class=\"language-typescript\">export type LogFormatFn = (\n  level: LogLevel,\n  category: string,\n  msg: string\n) =&gt; string;<\/code><\/pre>\n<p>Also, as functions cannot be used as tokens, an <code>InjectionToken<\/code> is introduced:<\/p>\n<pre><code class=\"language-typescript\">export const LOG_FORMATTER = new InjectionToken&lt;LogFormatter | LogFormatFn&gt;(\n  &quot;LOG_FORMATTER&quot;\n);<\/code><\/pre>\n<p>This <code>InjectionToken<\/code> supports both class-based <code>LogFormatter<\/code> as well as functional ones. This prevents breaking existing code. As a consequence of supporting both, <code>provideLogger<\/code> needs to treat both cases in a slightly different way: <\/p>\n<pre><code class=\"language-typescript\">export function provideLogger(config: Partial&lt;LoggerConfig&gt;, ...features: LoggerFeature[]): EnvironmentProviders {\n\n    const merged = { ...defaultConfig, ...config};\n\n    [...]\n\n    return makeEnvironmentProviders([\n        LoggerService,\n        {\n            provide: LoggerConfig,\n            useValue: merged\n        },\n\n        \/\/ Register LogFormatter\n        \/\/  - Functional LogFormatter:  useValue\n        \/\/  - Class-based LogFormatters: useClass\n        (typeof merged.formatter === &#039;function&#039; ) ? {\n            provide: LOG_FORMATTER,\n            useValue: merged.formatter\n        } : {\n            provide: LOG_FORMATTER,\n            useClass: merged.formatter\n        },\n\n        merged.appenders.map(a =&gt; ({\n            provide: LOG_APPENDERS,\n            useClass: a,\n            multi: true\n        })),\n        [...]\n    ]);\n}<\/code><\/pre>\n<p>While class-based services are registered with <code>useClass<\/code>, <code>useValue<\/code> is the right choice for their functional counterparts.<\/p>\n<p>Also, the consumers of the <code>LogFormatter<\/code> need to be prepared for both the functional as well as class-based approach:<\/p>\n<pre><code class=\"language-typescript\">@Injectable()\nexport class LoggerService {\n  private appenders = inject(LOG_APPENDERS);\n  private formatter = inject(LOG_FORMATTER);\n  private config = inject(LoggerConfig);\n\n  [...]\n\n  private format(level: LogLevel, category: string, msg: string): string {\n    if (typeof this.formatter === &#039;function&#039;) {\n        return this.formatter(level, category, msg);\n    }\n    else {\n        return this.formatter.format(level, category, msg);\n    }\n  }\n\n  log(level: LogLevel, category: string, msg: string): void {\n    if (level &lt; this.config.level) {\n      return;\n    }\n\n    const formatted = this.format(level, category, msg);\n\n    [...]\n  }\n  [...]\n}<\/code><\/pre>\n<h3>Occurrences and Variations<\/h3>\n<ul>\n<li>The <code>HttpClient<\/code> allows using functional interceptors. They are registered via a feature (see pattern <em>Feature<\/em>).<\/li>\n<li>The <code>Router<\/code> allows using functions for implementing guards and resolvers.<\/li>\n<\/ul>\n<h2>What's next? More on Architecture!<\/h2>\n<p>Please find more information on enterprise-scale Angular architectures in our free eBook (5th edition, 12 chapters): <\/p>\n<ul>\n<li>According to which criteria can we subdivide a huge application into sub-domains?<\/li>\n<li>How can we make sure, the solution is maintainable for years or even decades?<\/li>\n<li>Which options from Micro Frontends are provided by Module Federation?<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.angulararchitects.io\/book\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2022\/12\/cover-5th-small.png\" alt=\"free ebook\"><\/a><\/p>\n<p>Feel free to <a href=\"https:\/\/www.angulararchitects.io\/en\/book\">download it here<\/a> now!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Together with Standalone Components, the Angular team introduced Standalone APIs. They allow for setting up libraries in a more lightweight way. Examples of libraries currently providing Standalone APIs are the HttpClient and the Router. Also, NGRX is an early adopter of this idea. In this article, I present several patterns for writing custom Standalone APIs [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":6849,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-6848","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-unkategorisiert"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Patterns for Custom Standalone APIs in Angular - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Patterns for Custom Standalone APIs in Angular\" \/>\n<meta property=\"og:description\" content=\"Learn how to implement professional Standalone APIs for your reusable libraries.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2023-01-16T12:15:12+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/sm-1-1.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1000\" \/>\n\t<meta property=\"og:image:height\" content=\"500\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Manfred Steyer, GDE\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:title\" content=\"Patterns for Custom Standalone APIs in Angular\" \/>\n<meta name=\"twitter:description\" content=\"Learn how to implement professional Standalone APIs for your reusable libraries.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/sm-1-1.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@daniel\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer, GDE\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\"},\"author\":{\"name\":\"Manfred Steyer, GDE\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951\"},\"headline\":\"Patterns for Custom Standalone APIs in Angular\",\"datePublished\":\"2023-01-16T12:15:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\"},\"wordCount\":1914,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg\",\"articleSection\":[\"Unkategorisiert\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\",\"name\":\"Patterns for Custom Standalone APIs in Angular - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg\",\"datePublished\":\"2023-01-16T12:15:12+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg\",\"width\":1000,\"height\":1000},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Patterns for Custom Standalone APIs in Angular\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951\",\"name\":\"Manfred Steyer, GDE\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer, GDE\"},\"sameAs\":[\"https:\/\/x.com\/daniel\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Patterns for Custom Standalone APIs in Angular - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/","og_locale":"en_US","og_type":"article","og_title":"Patterns for Custom Standalone APIs in Angular","og_description":"Learn how to implement professional Standalone APIs for your reusable libraries.","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/","og_site_name":"ANGULARarchitects","article_published_time":"2023-01-16T12:15:12+00:00","og_image":[{"width":1000,"height":500,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/sm-1-1.jpg","type":"image\/jpeg"}],"author":"Manfred Steyer, GDE","twitter_card":"summary_large_image","twitter_title":"Patterns for Custom Standalone APIs in Angular","twitter_description":"Learn how to implement professional Standalone APIs for your reusable libraries.","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/sm-1-1.jpg","twitter_creator":"@daniel","twitter_misc":{"Written by":"Manfred Steyer, GDE","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/"},"author":{"name":"Manfred Steyer, GDE","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951"},"headline":"Patterns for Custom Standalone APIs in Angular","datePublished":"2023-01-16T12:15:12+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/"},"wordCount":1914,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg","articleSection":["Unkategorisiert"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/","name":"Patterns for Custom Standalone APIs in Angular - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg","datePublished":"2023-01-16T12:15:12+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/01\/shutterstock-1621283953.jpg","width":1000,"height":1000},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/patterns-for-custom-standalone-apis-in-angular\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Patterns for Custom Standalone APIs in Angular"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/15628efa7af4475ffaaeeb26c5112951","name":"Manfred Steyer, GDE","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a0b59539674d8b71ea1c1f4764b11244b5f499203f1d11b40f37d8f3f90be033?s=96&d=mm&r=g","caption":"Manfred Steyer, GDE"},"sameAs":["https:\/\/x.com\/daniel"]}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/6848","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=6848"}],"version-history":[{"count":0,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/6848\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/6849"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=6848"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=6848"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=6848"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}