Prior art and why now
This request was previously raised as #34605 (2020) and PR #34606, both closed as not_planned. The team's reasoning at the time was that they did not want to expand Angular's API surface further.
Several things have changed since that decision:
NgOptimizedImage already manages <link> elements internally — the directive emits <link rel="preload"> hints for priority images. The Angular team has therefore already accepted programmatic <link> management as a legitimate concern inside the framework; a shared, public service would make the same capability available to application code.
- Angular SSR (
@angular/ssr) has become a first-class citizen. Managing canonical URLs, preconnect hints, and Web App Manifest links is now a standard SSR task that developers encounter on virtually every production Angular application.
- Incremental hydration and partial rendering (Angular v17+) increase the importance of
<link rel="modulepreload"> and <link rel="preload"> management in application code.
- A reference implementation is available as
@grandgular/link, demonstrating a stable, well-tested API design that mirrors Meta and Title.
Description
Angular ships Meta and Title as first-class services for managing document metadata, yet there is no equivalent for <link> elements. Developers who need to manage canonical URLs, preload hints, stylesheets, or Web App Manifest links currently have to either:
- Manipulate the DOM directly (breaks Angular SSR)
- Reach for third-party libraries
- Write their own
DOCUMENT + Renderer2 wrapper
A Link service would complete Angular's "document-head management" story.
Describe the solution you'd like
LinkDefinition
A type alias covering every standardised attribute of the HTML <link> element, with an index signature for non-standard attributes. Deprecated HTML5 attributes (charset, rev, target) are kept for legacy compatibility, matching the approach taken by MetaDefinition which also retains deprecated fields.
/**
* Represents the attributes of an HTML `<link>` element.
*
* @see [HTML link element](https://developer.mozilla.org/docs/Web/HTML/Element/link)
* @see {@link Link}
*
* @publicApi
*/
export type LinkDefinition = {
as?: string;
blocking?: string;
charset?: string; // deprecated in HTML5, retained for legacy
crossorigin?: string;
disabled?: string;
fetchpriority?: string;
href?: string;
hreflang?: string;
imagesizes?: string;
imagesrcset?: string;
integrity?: string;
media?: string;
nonce?: string;
referrerpolicy?: string;
rel?: string;
rev?: string; // deprecated in HTML5, retained for legacy
sizes?: string;
target?: string; // deprecated in HTML5, retained for legacy
title?: string;
type?: string;
} & {
[prop: string]: string;
};
Link service
/**
* A service for managing HTML `<link>` tags.
*
* Properties of the `LinkDefinition` object match the attributes of the
* HTML `<link>` tag. These tags are used for canonical URLs, stylesheets,
* preload/prefetch hints, Web App Manifest references, and more.
*
* To identify specific `<link>` tags in a document, use an attribute selection
* string in the format `"tag_attribute='value string'"`.
* For example, an `attrSelector` value of `"rel='canonical'"` matches a tag
* whose `rel` attribute has the value `"canonical"`.
* Selectors are used with the `querySelector()` Document method,
* in the format `link[{attrSelector}]`.
*
* @see [HTML link element](https://developer.mozilla.org/docs/Web/HTML/Element/link)
* @see [Document.querySelector()](https://developer.mozilla.org/docs/Web/API/Document/querySelector)
*
* @publicApi
*/
@Injectable({ providedIn: 'root' })
export class Link {
/**
* Retrieves or creates a specific `<link>` tag element in the current HTML document.
* @param tag The definition of a `<link>` element to match or create.
* @param forceCreation True to create a new element without checking whether one already exists.
*/
addTag(tag: LinkDefinition, forceCreation?: boolean): HTMLLinkElement | null;
/**
* Retrieves or creates a set of `<link>` tag elements in the current HTML document.
*/
addTags(tags: LinkDefinition[], forceCreation?: boolean): HTMLLinkElement[];
/**
* Retrieves a `<link>` tag element in the current HTML document.
* @param attrSelector The tag attribute and value to match against, in the format
* `"tag_attribute='value string'"`.
*/
getTag(attrSelector: string): HTMLLinkElement | null;
/**
* Retrieves a set of `<link>` tag elements in the current HTML document.
*/
getTags(attrSelector: string): HTMLLinkElement[];
/**
* Modifies an existing `<link>` tag element in the current HTML document,
* or creates a new one if not found.
*/
updateTag(tag: LinkDefinition, selector?: string): HTMLLinkElement | null;
/**
* Removes an existing `<link>` tag element from the current HTML document.
*/
removeTag(attrSelector: string): void;
/**
* Removes a specific `<link>` element from the DOM.
*/
removeTagElement(link: HTMLLinkElement): void;
}
Usage examples
readonly link = inject(Link);
// Canonical URL
this.link.addTag({ rel: 'canonical', href: 'https://example.com/page' });
// Preload a critical font
this.link.addTag({
rel: 'preload',
as: 'font',
type: 'font/woff2',
href: '/fonts/brand.woff2',
crossorigin: 'anonymous',
});
// Update canonical — no duplicate is created
this.link.updateTag({ rel: 'canonical', href: 'https://example.com/new-page' });
// Remove a stylesheet
this.link.removeTag('rel="stylesheet"');
Placement
The service fits naturally inside the existing platform-browser package with no new entry points and no new peer dependencies:
packages/platform-browser/
src/
browser/
meta.ts ← existing
title.ts ← existing
link.ts ← NEW
platform-browser.ts ← add: export {Link, LinkDefinition} from './browser/link';
public_api.ts ← already re-exports everything from platform-browser.ts
goldens/public-api/
platform-browser_api.d.ts ← needs to be updated (yarn bazel run //tools/public_api_guard:platform-browser_api.accept)
SSR compatibility
The proposed implementation uses Renderer2 (via RendererFactory2) and the DOCUMENT token exclusively. This is a deliberate improvement over the existing Meta service which relies on ɵDomAdapter (internal, unstable API): Renderer2 is public, integrates with custom/test renderers, and is consistent with how third-party Angular libraries are expected to interact with the DOM.
Alternatives considered
- Extending
NgOptimizedImage: too narrow in scope; canonical URLs and manifest links have nothing to do with images.
- Keeping it as a community library: the same argument could have been made against
Meta and Title. Document-head management is a sufficiently common concern to warrant first-class framework support.
- Using
@angular/platform-browser's DomAdapter directly: DomAdapter is marked ɵ (internal) and is not recommended for application or library use.
Breaking changes
None. This is a purely additive change.
Prior art / proof of concept
A reference implementation is published as @grandgular/link (source: https://github.com/Grandgular/link). It has been used in production applications and its test suite covers all public API methods, SSR rendering, deduplication, and edge cases.
Open questions for the Angular team
Prior art and why now
This request was previously raised as #34605 (2020) and PR #34606, both closed as
not_planned. The team's reasoning at the time was that they did not want to expand Angular's API surface further.Several things have changed since that decision:
NgOptimizedImagealready manages<link>elements internally — the directive emits<link rel="preload">hints for priority images. The Angular team has therefore already accepted programmatic<link>management as a legitimate concern inside the framework; a shared, public service would make the same capability available to application code.@angular/ssr) has become a first-class citizen. Managing canonical URLs, preconnect hints, and Web App Manifest links is now a standard SSR task that developers encounter on virtually every production Angular application.<link rel="modulepreload">and<link rel="preload">management in application code.@grandgular/link, demonstrating a stable, well-tested API design that mirrorsMetaandTitle.Description
Angular ships
MetaandTitleas first-class services for managing document metadata, yet there is no equivalent for<link>elements. Developers who need to manage canonical URLs, preload hints, stylesheets, or Web App Manifest links currently have to either:DOCUMENT+Renderer2wrapperA
Linkservice would complete Angular's "document-head management" story.Describe the solution you'd like
LinkDefinitionA type alias covering every standardised attribute of the HTML
<link>element, with an index signature for non-standard attributes. Deprecated HTML5 attributes (charset,rev,target) are kept for legacy compatibility, matching the approach taken byMetaDefinitionwhich also retains deprecated fields.LinkserviceUsage examples
Placement
The service fits naturally inside the existing
platform-browserpackage with no new entry points and no new peer dependencies:SSR compatibility
The proposed implementation uses
Renderer2(viaRendererFactory2) and theDOCUMENTtoken exclusively. This is a deliberate improvement over the existingMetaservice which relies onɵDomAdapter(internal, unstable API):Renderer2is public, integrates with custom/test renderers, and is consistent with how third-party Angular libraries are expected to interact with the DOM.Alternatives considered
NgOptimizedImage: too narrow in scope; canonical URLs and manifest links have nothing to do with images.MetaandTitle. Document-head management is a sufficiently common concern to warrant first-class framework support.@angular/platform-browser'sDomAdapterdirectly:DomAdapteris markedɵ(internal) and is not recommended for application or library use.Breaking changes
None. This is a purely additive change.
Prior art / proof of concept
A reference implementation is published as
@grandgular/link(source: https://github.com/Grandgular/link). It has been used in production applications and its test suite covers all public API methods, SSR rendering, deduplication, and edge cases.Open questions for the Angular team
platform-browservs. a newplatform-browser/linksecondary entry pointLinkDefinition(parallel toMetaDefinition) vs.LinkAttributesRenderer2-based implementation is preferred overɵDomAdapterfor a new service