-
Notifications
You must be signed in to change notification settings - Fork 26.5k
feat(compiler): allow self-closing tags on custom elements #48535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
d4e1236
to
2e5be73
Compare
I’m sorry if the answer to my question is in the source code: will the compiler expand self-closed tags to regular tags to don't break the specification for custom elements? |
Yes, |
In the issue you linked, @IgorMinar explained why this feature could not "just be added" and outlined that there would be an impact to the ecosystem.
As far as I understand, supporting self-closing tags (that do not exist in HTML) would mean that it would no longer be possible to use HTML parsers like the DOM to parse Angular templates. Did you evaluate the impact of this change and think about how it could be rolled out smoothly? |
@Yogu from the other side, developers are not forced to use self-closed tags only. While tools are being updated to this change, we can still use our existing code and write regular tags. If Angular will not make a step in this direction, tools will never follow. |
Angular templates aren't being parsed by the DOM. We have our own parser and compiler that turn them into JavaScript function calls. |
Sorry, but this full on ignores the main point. This introduces the first HTML non-compliant change. That means that all existing tooling that depends on HTML compliance of Angular templates needs to be changed/rewritten to support this feature. I cite from the official Angular documentation itself:
|
@TillFProtzek current capabilities of tooling should not stop Angular evolution. Tools should adapt to changes and evolution, that's the reality. Somehow they adapted to standalone components, directive selectors, (event) [binding] syntax - they weren't stopped by the fact that it's not part of HTML. And existing code will not be converted to "self-closing tags only" overnight. |
What parser should people use when they write update schematics? We currently use parse5 for that. Is it possible to use the angular parser for that, does it have public api? |
@e-oz it's not the point if something was part or not part of HTML. All syntax extension of Angular are/were HTML syntax compliant. All points you mentioned are HTML syntax compliant. Templates until now could be parsed by any HTML-parser. It seems that the full scope of that change is missed in regards of reward/risk. Formatters, Linters, sophisticated dev-tools - all have to change if the developers start to use the feature or adapt a template using it. @samclaus the originator of the feature-request got convinced in the discussion there that it is perhaps not a good idea. So convinced that the only mention of the feature request afterwards, before this pull request out of the blue, is from himself. Pointing out again to what he learned about the risk of changing HTML compliance of the templates. |
I'm also not sure where this PR is coming from? Angular HTML template files are HTML-spec compliant and I believe that was the point of even using HTML to begin with as opposed to JSX or something else, right? |
Hello all! I'm super excited for this feature and I love to see the discussion here. Yes, this is a departure from templates being strictly HTML-compliant. We're fully aware of that - it's an intentional choice on our part, after thoughtful analysis of the tradeoffs involved. We're not making this decision purely for the syntactic sugar of It enables some bigger DX improvements in templatesSyntactic sugar for self-closing tags is a small DX improvement of its own, but it's not the main motivation here. We're looking at a number of other improvements we can make to improve the template authoring experience. Things like improving the ergonomics of control flow, resolving longstanding inconsistencies in some of the binding syntax, and improving support for TypeScript as well as other tooling such as linters - all of which can benefit from native syntax in templates. These are early thoughts, and will be bringing these kinds of changes to RFC as we nail down the details (although these things take time). Templates already are their own languageToday, Angular templates are syntactically HTML, but semantically distinct. Even if Consider an HTML formatter running on the template expression In practice, we've seen some tools avoid this problem by using Angular's own template parser to parse templates. The popular This specific change is minimally impactfulSelf-closing tags are an optional language feature. If a project wants to enforce that its templates are parseable as plain HTML, it can do so (likely via a lint rule). All tooling and parsers we've tested has been perfectly usable with self-closing component tags. In fact, they're often tripped up by other aspects of Angular syntax (case sensitivity, SummaryTo be clear, we like HTML. Angular templates are HTML-flavored, and we have no plans to change that - writing templates should feel like writing HTML. But Angular templates are special already, and the ergonomic benefits of relaxing strict compliance with the HTML spec from a syntactic standpoint are significant. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@alxhub already mentioned most of what I would have said here.
The idea of "valid HTML templates" has historically been an important one to make sure that HTML documents rendered by the browser render as intended. Angular templates, though, are never rendered directly by the browser. An Angular template is always compiled into JavaScript instructions that manually create a DOM structure (via underlying APIs like document.createElement
). While some developers may appreciate the concept of concept/experience of authoring spec-valid HTML, it does not actually have any bearing whatsoever on what's ultimately rendered into the DOM.
This feature in particular is completely optional, and people can continue using closing tags for custom elements, so we don't see this as breaking.
While we do expect to make changes to the template syntax over the years, we will always pay close attention to the stability and backwards of the ecosystem.
Thank you for these detailed responses! I see that you're not making this decision lightheartedly. I just would like to draw attention to the question asked by @kroeder again:
If library and tool authors are expected to support the new template syntax, it would be very helpful to provide a stable parser (and modification / printer) API. This is especially relevant when writing schematics that update templates which previously could use standard HTML parsers and printers. While a team developing an app can just decide not to use this new feature, library and tool authors are not in this luxurious position - if they want to claim say Angular 16 compatibility, they need to support the new template syntax. Edit: Sorry, I somehow missed that @alxhub already addressed this point and provided angular-eslint as an example. The package
If it's already planned to make some of these APIs public, maybe it would make sense to align the template syntax changes with the release of these public APIs? (Or keep the new template syntax behind an experimental compiler options so libraries and tools could still claim Angular compatibility while they don't support it yet)
The html parser parse5 is not (demonstration) and it would not be HTML-compliant if it was. |
2e5be73
to
e33114a
Compare
@@ -138,9 +143,15 @@ export function getHtmlTagDefinition(tagName: string): HtmlTagDefinition { | |||
'textarea': new HtmlTagDefinition( | |||
{contentType: TagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true}), | |||
}; | |||
|
|||
new DomElementSchemaRegistry().allKnownElementNames().forEach(knownTagName => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change here and to the DEFAULT_TAG_DEFINITION
are the key ones. Now we mark any element from the DOM schema, that hasn't been added to the tag definition already, as not being able to self-close, whereas all unknown elements are allowed to self-close.
@@ -172,22 +170,6 @@ export class CssSelector { | |||
this.element = element; | |||
} | |||
|
|||
/** Gets a template string for an element that matches the selector. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context here is that this would've caused a circular import with my latest changes. I removed the code, because it wasn't being referenced anywhere except the unit tests for the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@alxhub I've reworked the PR so that only non-native elements allow self-closing. |
d5b7294
to
ddf2559
Compare
Allows for self-closing tags to be used for non-native tag names, e.g. `<foo [input]="bar"></foo>` can now be written as `<foo [input]="bar"/>`. Native tag names still have to have closing tags. Fixes angular#39525.
ddf2559
to
6cd07d7
Compare
@@ -172,22 +170,6 @@ export class CssSelector { | |||
this.element = element; | |||
} | |||
|
|||
/** Gets a template string for an element that matches the selector. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Is there any (new or update to existing) documentation to accompany this new feature? |
@crisbeto quick question: it looks like this change covers DOM elements, should we allow |
@AndrewKushnir unless I'm missing something, this should allow |
@crisbeto yeah, you are right, thanks for the reply 👍 |
I hope "path" is not in the list of known tags - it would help SVG components. |
This PR was merged into the repository by commit a532d71. |
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular/animations](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fanimations/15.0.4/15.1.0) | | [@angular/common](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fcommon/15.0.4/15.1.0) | | [@angular/compiler](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fcompiler/15.0.4/15.1.0) | | [@angular/compiler-cli](https://github.com/angular/angular/tree/main/packages/compiler-cli) ([source](https://github.com/angular/angular)) | devDependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fcompiler-cli/15.0.4/15.1.0) | | [@angular/core](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fcore/15.0.4/15.1.0) | | [@angular/forms](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fforms/15.0.4/15.1.0) | | [@angular/platform-browser](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser/15.0.4/15.1.0) | | [@angular/platform-browser-dynamic](https://github.com/angular/angular) | dependencies | minor | [`15.0.4` -> `15.1.0`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser-dynamic/15.0.4/15.1.0) | --- ### Release Notes <details> <summary>angular/angular</summary> ### [`v15.1.0`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#​1510-2023-01-10) [Compare Source](angular/angular@15.0.4...15.1.0) #### Deprecations ##### router - CanLoad guards in the Router are deprecated. Use CanMatch instead. - router writable properties The following strategies are meant to be configured by registering the application strategy in DI via the `providers` in the root `NgModule` or `bootstrapApplication`: - `routeReuseStrategy` - `titleStrategy` - `urlHandlingStrategy` The following options are meant to be configured using the options available in `RouterModule.forRoot` or `provideRouter`. - `onSameUrlNavigation` - `paramsInheritanceStrategy` - `urlUpdateStrategy` - `canceledNavigationResolution` The following options are available in `RouterModule.forRoot` but not available in `provideRouter`: - `malformedUriErrorHandler` - This was found to not be used anywhere internally. - `errorHandler` - Developers can instead subscribe to `Router.events` and filter for `NavigationError`. ##### common | Commit | Type | Description | | -- | -- | -- | | [fe50813664](angular/angular@fe50813) | feat | Add BrowserPlatformLocation to the public API ([#​48488](angular/angular#48488)) | | [2f4f0638c7](angular/angular@2f4f063) | fix | Add data attribtue to NgOptimizedImage ([#​48497](angular/angular#48497)) | ##### compiler | Commit | Type | Description | | -- | -- | -- | | [a532d71975](angular/angular@a532d71) | feat | allow self-closing tags on custom elements ([#​48535](angular/angular#48535)) | | [caf7228f8a](angular/angular@caf7228) | fix | resolve deprecation warning ([#​48652](angular/angular#48652)) | | [33f35b04ef](angular/angular@33f35b0) | fix | type-only symbols incorrectly retained when downlevelling custom decorators ([#​48638](angular/angular#48638)) | ##### compiler-cli | Commit | Type | Description | | -- | -- | -- | | [caedef0f5b](angular/angular@caedef0) | fix | update `@babel/core` dependency and lock version ([#​48634](angular/angular#48634)) | ##### core | Commit | Type | Description | | -- | -- | -- | | [6acae1477a](angular/angular@6acae14) | feat | Add `TestBed.runInInjectionContext` to help test functions which use `inject` ([#​47955](angular/angular#47955)) | | [38421578a2](angular/angular@3842157) | feat | Make the `isStandalone()` function available in public API ([#​48114](angular/angular#48114)) | | [dd42974b07](angular/angular@dd42974) | feat | support TypeScript 4.9 ([#​48005](angular/angular#48005)) | ##### forms | Commit | Type | Description | | -- | -- | -- | | [8aa8b4b77c](angular/angular@8aa8b4b) | fix | Form provider FormsModule.withConfig return a FormsModule ([#​48526](angular/angular#48526)) | ##### language-service | Commit | Type | Description | | -- | -- | -- | | [5f0b53c735](angular/angular@5f0b53c) | feat | Allow auto-imports to suggest multiple possible imports. ([#​47787](angular/angular#47787)) | | [6a8ea29a04](angular/angular@6a8ea29) | fix | expose `package.json` for vscode extension resolution ([#​48678](angular/angular#48678)) | | [ce8160ecb2](angular/angular@ce8160e) | fix | Prevent crashes on unemitable references ([#​47938](angular/angular#47938)) | | [e615b598ba](angular/angular@e615b59) | fix | ship `/api` entry-point ([#​48670](angular/angular#48670)) | | [6ce7d76a0e](angular/angular@6ce7d76) | fix | update packages/language-service/build.sh script to work with vscode-ng-language-service's new Bazel build ([#​48663](angular/angular#48663)) | ##### localize | Commit | Type | Description | | -- | -- | -- | | [a1a8e91eca](angular/angular@a1a8e91) | fix | add triple slash type reference on `@angular/localize` on \`ng add ([#​48502](angular/angular#48502)) | ##### migrations | Commit | Type | Description | | -- | -- | -- | | [cc284afbbc](angular/angular@cc284af) | fix | combine newly-added imports in import manager ([#​48620](angular/angular#48620)) | ##### router | Commit | Type | Description | | -- | -- | -- | | [228e992db7](angular/angular@228e992) | docs | Deprecate canLoad guards in favor of canMatch ([#​48180](angular/angular#48180)) | | [0a8b8a66cd](angular/angular@0a8b8a6) | docs | Deprecate public members of Router that are meant to be configured elsewhere ([#​48006](angular/angular#48006)) | | [332461bd0c](angular/angular@332461b) | feat | Add ability to override `onSameUrlNavigation` default per-navigation ([#​48050](angular/angular#48050)) | | [f58ad86e51](angular/angular@f58ad86) | feat | Add feature provider for enabling hash navigation ([#​48301](angular/angular#48301)) | | [73f03ad2d2](angular/angular@73f03ad) | feat | Add new NavigationSkipped event for ignored navigations ([#​48024](angular/angular#48024)) | | [3fe75710d9](angular/angular@3fe7571) | fix | page refresh should not destroy history state ([#​48540](angular/angular#48540)) | #### Special Thanks Alan Agius, Alex Castle, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Bob Watson, Charles Lyding, Derek Cormier, Doug Parker, Dylan Hunn, George Kalpakas, Greg Magolan, Jessica Janiuk, JiaLiPassion, Joey Perrott, Kristiyan Kostadinov, Matthieu Riegler, Paul Gschwendtner, Pawel Kozlowski, Renan Ferro, Tim Gates, Vadim, Virginia Dooley, ced, mgechev, piyush132000, robertIsaac and sr5434 <!-- CHANGELOG SPLIT MARKER --> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC45Ny41IiwidXBkYXRlZEluVmVyIjoiMzQuOTkuMiJ9--> Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1719 Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
Nice, but how does this improve Angular in any way? |
@maximilian27 See #48535 (comment)
|
…8535) Allows for self-closing tags to be used for non-native tag names, e.g. `<foo [input]="bar"></foo>` can now be written as `<foo [input]="bar"/>`. Native tag names still have to have closing tags. Fixes angular#39525. PR Close angular#48535
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Allows for self-closing tags to be used for non-native tag names, e.g.
<foo [input]="bar"></foo>
can now be written as<foo [input]="bar"/>
. Native tag names still have to have closing tags.Fixes #39525.