Skip to content
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

Add mechanism for generating or obtaining unique IDs for nodes within a component #5145

Closed
bryanforbes opened this issue Nov 5, 2015 · 11 comments

Comments

@bryanforbes
Copy link

While using IDs in applications is generally frowned upon, there are certain cases where it cannot be avoided. For instance, for, aria-labelledby, aria-describedby, and aria-flowto (to name a few) all require an ID for an element as their value. And while most forms can be written so that for is not needed on <label> elements, the aria-* properties cannot be done that way. Consider a modal dialog component:

@Component({
    selector: "modal-dialog",
    host: {
        "aria-labelledby": "???",
        "role": "dialog"
    },
    template: ```
        <div class="modal-title" [id]="???">
            <ng-content select="[title]"></ng-content>
        </div>
        <div class="modal-content">
            <ng-content select="[content]"></ng-content>
        </div>
    ```
})
export class ModalDialog {
}

Dijit exposes the widget's unique ID as the id property which can be used in widget templates to generate unique element IDs within widgets. Something similar could be done in ng2 using either a property or a pipe. I could see either of the following syntaxes:

<div class="modal-title" [id]="__id + '_titleNode'">
    <ng-content select="[title]"></ng-content>
</div>
<div class="modal-content">
    <ng-content select="[content]"></ng-content>
</div>

OR

<div class="modal-title" [id]="'titleNode' | prefixComponentId">
    <ng-content select="[title]"></ng-content>
</div>
<div class="modal-content">
    <ng-content select="[content]"></ng-content>
</div>

The ID used above would also need to be in the host object passed to the @Component() annotation. I also want to make sure I note that these two suggestions are just that: suggestions. I'm more concerned about the ability to get a unique ID for a node than I am how that is accomplished as long as it is not convoluted.

@HonnKing
Copy link

Looking for the same thing but for name. I have a component that contains radio buttons and if I have the component multiple times on the page I don't want the radio button groups to overlap, so I need to generate a unique name for each.

@zoechi
Copy link
Contributor

zoechi commented May 23, 2016

I think this should be quite easy with a custom directive and some global counter variable (either a static field or a field in a shared service).

@HonnKing
Copy link

Sure, any form of globally available state would solve it, but considering angular already has a unique id per component for view encapsulation it makes sense to expose that to the template (or the component rather).

@pkozlowski-opensource
Copy link
Member

I've discussed this with several people working on widgets implementation (Material, Bootstrap etc.) and for now the consensus is that we don't need any particular service from the framework to cover this use case. You can easily have a per-component-unique ids using a patter shown here: ng-bootstrap/ng-bootstrap@b06d56c

@NathanLawrence
Copy link

I know this issue just got closed, but I do think this is worth having a greater discussion about.

There's a philosophical stance I could see people taking here in that the primary purpose of creating something like this would be to better expose the DOM to manipulation outside Angular's purview, which obviously creates room for all sorts of very difficult to trace bugs.

That said, there are times when this is just a necessity whether we like it or not. Users making components based on D3 or other visualization libraries Angular has no parallel to internally are going to need ways to select elements natively with those libraries. There really is no getting around this. By not providing an officially blessed solution, what we're going to wind up with is a fragmented mess of services and random number generators inside of constructors and all kinds of other hacky ways to create solutions that might fail in edge cases anyway.

@renehamburger
Copy link

I agree that a built-in solution would be ideal! But if component names are unique within the app, they can also be used to generate such a unique ID:

Here's a minimalistic getId() function that concatenates the component's name either with a given identifier or a unique id generated by lodash

export class GetId {
  protected getId(id: number|string = _.uniqueId()): string {
    return _.lowerFirst(this.constructor.name) + '_' + id;
  }
}

class MyComponent extends GetId {
  ...    
}

which can then be used throughout the template to generate app-wide unique IDs

<label [attr.for]="getId('name')">Name</label>
<input [id]="getId('name')" type="text">
...
<label [attr.for]="getId('email')">E-mail</label>
<input [id]="getId('email')" type="email">

@renehamburger
Copy link

Actually, this.constructor.name won't be unique after minification! The component's selector stored in its metadata could be used instead (i.e. Reflect['getOwnMetadata']('annotations', this.constructor)[0].selector), but that may not survive AOT compilation with "skipMetadataEmit": true. In other words, a built-in solution would be much appreciated!

@BradyNadeau
Copy link

@renehamburger I completely agree with what you said, but fortunately i can use Refelect in this case... I feel a bit dirty doing it but it works for now...
I'm needing to have this functionality because I have a single SiteMap for all of the pages in my application defining their Title, Icon, and Role Permissions that I can then use with a Authorization Provider to lookup the page I'm about to load and determine if the user has access to that page.

@GregBaggings
Copy link

This unique id generation should be mandatory for the framework to ensure easier testing on all levels of the testing pyramid. Thousands of developers and test automation engineers would be pleased by a global - common solution.

@BenRacicot
Copy link

BenRacicot commented May 24, 2019

This is definitely a thing Angular devs need. For now I'm just using

id = Math.random().toString(36).substring(2)

I had an idea about accessing the dynamically generated _nghost name but there doesn't seem to be a way to access it. Oh well.

The docs regarding the _nghost:

There are two kinds of generated attributes:

An element that would be a shadow DOM host in native encapsulation has a generated _nghost attribute. This is typically the case for component host elements.
An element within a component's view has a _ngcontent attribute that identifies to which host's emulated shadow DOM this element belongs.
The exact values of these attributes aren't important. They are automatically generated and you never refer to them in application code. But they are targeted by the generated component styles, which are in the section of the DOM:

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants