angular: NG0912 error after migrating (to 16) multiple components with no selector
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
Yes
Description
Components generated with the CLI (pre 16.x or 16.x) using --skip-selector
result in the browser displaying NG0912 error, claiming that multiple of my components have colliding selector ng-component
, when using Angular 16. This error is not present in at least 13, 14, and 15.
The components in question all look like this:
import { Component } from '@angular/core';
@Component({
templateUrl: './foo-with-no-selector.component.html',
styleUrls: ['./foo-with-no-selector.component.scss'],
})
export class FooWithNoSelector{ ... some stuff }
@Component({
templateUrl: './bar-with-no-selector.component.html',
styleUrls: ['./bar-with-no-selector.component.scss'],
})
export class BarWithNoSelector{ ... some stuff }
These components are used only in Routing (or manually created as dialogs using material).
Afaik the convention for route only components was to omit selector
. Is this change intentional or is it documented somewhere?
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
NG0912: Component ID generation collision detected. Components 'FooWithNoSelector' and 'BarWithNoSelector' with selector 'ng-component' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID. Find more at https://angular.io/errors/NG0912
Please provide the environment you discovered this bug in (run ng version
)
Angular CLI: 16.0.0
Node: 18.16.0
Package Manager: npm 9.6.4
OS: win32 x64
Angular: 16.0.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, material, material-luxon-adapter, platform-browser
... platform-browser-dynamic, router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1600.0
@angular-devkit/build-angular 16.0.0
@angular-devkit/core 16.0.0
@angular-devkit/schematics 16.0.0
@schematics/angular 16.0.0
rxjs 7.8.1
typescript 4.9.5
Anything else?
No response
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 22
- Comments: 44 (14 by maintainers)
Commits related to this issue
- fix(core): add additional component metadata to component ID generation This commit add `exportAs`, `signals`, `inputs` and `outputs` into account when generating a component ID. See: https://github... — committed to alan-agius4/angular by alan-agius4 a year ago
- fix(core): add additional component metadata to component ID generation (#50203) This commit add `exportAs`, `signals`, `inputs` and `outputs` into account when generating a component ID. See: https... — committed to angular/angular by alan-agius4 a year ago
- fix(core): add additional component metadata to component ID generation (#50203) This commit add `exportAs`, `signals`, `inputs` and `outputs` into account when generating a component ID. See: https... — committed to angular/angular by alan-agius4 a year ago
- fix(core): add additional component metadata to component ID generation (#50203) This commit add `exportAs`, `signals`, `inputs` and `outputs` into account when generating a component ID. See: https... — committed to sumitparakh/angular by alan-agius4 a year ago
- docs: added clarity in lifecycle event docs Closes #49686 Revert "docs(docs-infra): Remove unused annotation template (#50114)" (#50206) This reverts commit a1ca162fd6e8f6f5b1171e8d8f1c6b7b1973e353... — committed to sumitparakh/angular by sumitparakh a year ago
- docs: added clarity in lifecycle event docs Closes #49686 Revert "docs(docs-infra): Remove unused annotation template (#50114)" (#50206) This reverts commit a1ca162fd6e8f6f5b1171e8d8f1c6b7b1973e353... — committed to sumitparakh/angular by sumitparakh a year ago
- docs: added clarity in lifecycle event docs (#50145) Closes #49686 Revert "docs(docs-infra): Remove unused annotation template (#50114)" (#50206) This reverts commit a1ca162fd6e8f6f5b1171e8d8f1c6b7... — committed to angular/angular by sumitparakh a year ago
- docs: added clarity in lifecycle event docs (#50145) Closes #49686 Revert "docs(docs-infra): Remove unused annotation template (#50114)" (#50206) This reverts commit a1ca162fd6e8f6f5b1171e8d8f1c6b7... — committed to angular/angular by sumitparakh a year ago
- Angular 16 collision IDs * Angular 16 shows NG0912 for components that have equivalent metadata. This is "problematic" for the window components (intructions, FAQ, etc.) as they don't (and shouldn't)... — committed to Fasguy/MinecraftToolbox by Fasguy a year ago
But that’s just a hack, it’s not a solution. I should not have to add anything to a non-routable component to make it work.
This is still an ongoing issue with angular 16.1.1
I really do not want to add selectors/useless properties to my components to get rid of this error, but I want to know if I have 2 instances of the same selector.
The fact that you default to saying componentA and componentB with selector ‘ng-component’ generated the same component ID should lead you to either “hide” the error when the selector is ng-component, or add the whole class metadata
It’s quite annoying to have a bunch of spam in the console when your routing components don’t have selectors.
It would be nice if instead of just assigning “ng-component” to all selector-less components that you’d generate an imaginary selector based on the class name, or just use garbled nonsense. I don’t want to disable ng0912 because I find it quite useful to help debug some issues, but I also don’t want to add selectors to components that don’t need it, or components that shouldn’t be instanciated through selectors.
“”“Fixing”“” this by adding meaningless inputs, attributes or anything else is also a really bad smell and will lead to technical debt. I also don’t want to add
//angular big dum dum ignore line do not use do not remove unless warning
or something equivalent on top of “unnecessary” fixes@Sajyd Please tell me how to disable this ?)
Having the same issue. A solution would be to add an option to angular.json to disable it…
This change is pretty frightening. In all projects I was involved I always advocated for removing selectors on components that are included through a route or opened as an overlay. This should avoid that somebody includes a component into the template which is not intended for a certain component. This also reduces the number of components that are suggested by IDE. Now I will have to add something to this components just so they have an id. Would it be possible to disable this behavior for apps that do not use SSR?
It’s also a violation of Angular’s own style guide:
Style 06-03
Is there any movement on this? Part of a CI/CD pipeline will break a build if the word “error” appears in a particular log. Since this references this URL: https://angular.io/errors/NG0912 it is considering it an error. If @JeanMeche is correct, that this is NOT an error, then why is it referenced in an error URL? Seems to me, this is a defect. Routed components don’t have selectors and I don’t like the idea of adding arbitrary selectors or host bindings to work around this.
Could you explain what those cases are, and what the broken behavior would be?
Also, what is the component ID actually used for? I would have thought the component’s type would uniquely identify a component.
I doubt making the selector mandatory would be a good change. There are many examples where it’s not a useful thing, such as routing components and dialog components, where you really don’t want someone to just start “manually rendering” those components outside of their intended context. This is without considering all the apps this kind of change would break, we probably have around 100 “selector-less” components in our app.
I do think you hit the nail in the head when you say that the fingerprinting is incomplete.
The reason why this is a warning and not a hard error is because whilst in most cases this will not cause a runtime issue, there are cases cases when it will.
We did discuss the component ID generation at build time, but there are some challenges there based on symbol location due to potential differences in paths on OS (Windows vs Linux), multiple TS roots, etc… we are however, still very much looking into this.
This makes no sense, if the selector field is the best way to create a unique ID (using bindings, styles or other fields is a hack, since they can be similar between components as well), the only option from the angular team is to set the selector field to be mandatory and not optional.
It sounds like the mechanism for differentiating between components using a fingerprint of their metadata isn’t as robust and maybe a different method should be considered (file path maybe?).
Fair enough. My naive suggestion would be an md5 hash computed from the class name as a secondary identifier
So why is it just a warning and not a hard error?
The solution for a bunch of components that are used in component outlets and routes… is just sprinkle some random properties on top of them so they differentiate from each other… why isn’t this being caught during compilation and handled automatically?
We getting this error even with selector
I ran into this problem with an angular 16 app that uses material and has not been migrated to mdc components:
@JoostK, I think we could add
exportAs
, inputs and outputs.@JeanMeche, Indeed the name is not consistent between server and client bundles due to minification.
The name would indeed not be stable.
It seems like inputs/outputs are not currently included in the hash function, nor is
exportAs
. Especially the latter is interesting, as it is invisible to what ends up being rendered.@alan-agius4 What would you think of adding
componentDef.type.name
to the hashSelectors to reduce collisions ? Would that still work even after minifiers ?That would fix @alexaka1’s issue.
@JeanMeche In my opinion, more unnecessary extra things that I have to add. Ie.: adding a
host
makes no sense because the host would be therouter-outlet
or thecdk-overlay
in my use case. That also suggests behaviour for my component that doesn’t actually exist. Is there a way that it just works, without me going in and adding syntactic sugar to existing code? I’m just curious. If no, then okay, I don’t like this, but I accept the fact that my DX is regressing with this update.@alan-agius4 @JeanMeche Thanks for clearing it up! I would disagree that the metadata is identical though as the two components have different
templateUrl
andstyleUrls
. Also obviously these two components are not in the same file either, I just simplified it for the example.