What is causing my custom Angular 2 dropdown to not populate @ContentChildren?

Recreating the appearance of a Fabric-style dropdown for Angular 2 is my current project. There are two key elements that I need to focus on:

  1. I'm making use of ngModel
  2. The dropdown component is unaware of its children until queried. The dropdown items will be in another template, as it's the parent component that holds the necessary data for insertion.

This is what I've developed so far for the dropdown and dropdownItem components:

@Component({
    selector: "dropdown",
    template: ` <div class="ms-Dropdown" [ngClass]="{'ms-Dropdown--open': isOpen}">
                    <span class="ms-Dropdown-title" (click)="toggleDropdown()"> {{selectedName}} </span>
                    <ul class="ms-Dropdown-items">
                        <ng-content></ng-content>
                    </ul>
                </div>`,
    providers: [QueryList]
})
export class FabricDropdownComponent extends AbstractValueAccessor implements AfterContentInit {
    public selectedName: string;
    public isOpen: boolean;
    private subscriptions: Subscription[];
    constructor( @ContentChildren(FabricDropdownItemComponent) private items: QueryList<FabricDropdownItemComponent>) {
        super();
        this.subscriptions = [];
        this.selectedName = "Filler text, this should be replaced by 'Thing'";
        this.isOpen = false;
    }

    // HERE'S THE ISSUE: this.items remains an empty array, preventing access to child components.
    public ngAfterContentInit() {
        this.items.changes.subscribe((list: any) => {
            // Subscribing again on every change.
            this.subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
            this.subscriptions = [];
            this.items.forEach((item: FabricDropdownItemComponent) => {
                this.subscriptions.push(item.onSelected.subscribe((selected: INameValuePair) => {
                    this.value = selected.value;
                    this.selectedName = selected.name;
                    this.isOpen = false;
                }));
            });
        });

        // At initialization, display the *name* of the chosen value.
        // ONCE AGAIN: items array being empty causes inability to set initial value. What could be the issue?
        this.items.forEach((item: FabricDropdownItemComponent) => {
            if (item.value === this.value) {
                this.selectedName = item.name;
            }
        })
    }

    public toggleDropdown() { 
        this.isOpen = !this.isOpen;
    }
}

@Component({
    selector: "dropdownItem",
    template: `<li (click)="select()" class="ms-Dropdown-item" [ngClass]="{'ms-Dropdown-item--selected': isSelected }">{{name}}</li>`
})
export class FabricDropdownItemComponent implements OnInit {
    @Input() public name: string;
    @Input() public value: any;
    @Output() public onSelected: EventEmitter<INameValuePair>;
    public isSelected: boolean;
    constructor() {
        this.onSelected = new EventEmitter<INameValuePair>();
        this.isSelected = false;
    }

    public ngOnInit() {
        if (!this.name) {
            this.name = this.value.toString();
        }
    }

    public select() {
        this.onSelected.emit({ name: this.name, value: this.value });
        this.isSelected = true;
    }

    public deselect() {
        this.isSelected = false;
    }
}

(AbstractValueAccessor sourced from here.)

Below is how I've utilized these components within the application:

<dropdown [(ngModel)]="responseType" ngDefaultControl>
    <dropdownItem [value]="'All'"></dropdownItem>
    <dropdownItem *ngFor="let r of responseTypes" [value]="r.value" [name]="r.name"></dropdownItem>
</dropdown>

The problem arises when the dropdown's QueryList of @ContentChildren is consistently empty, resulting in no notifications upon clicking on dropdownItems. Why does the QueryList remain void and how might this be resolved? Have I overlooked something crucial here? (Alternatively, using a service for communication between dropdown and dropdownItem instead of a QueryList could be considered, but for now, why does the QueryList remain unpopulated?)

I attempted using @ViewChildren without success. Additional errors were encountered when adding FabricDropdownItemComponent to the directives of dropdown, including:

Error: Unexpected directive value 'undefined' on the View of component 'FabricDropdownComponent'
.

Plunker Link: https://plnkr.co/edit/D431ihORMR7etrZOBdpW?p=preview

Answer №1

Did you know there is an ongoing issue regarding @ContentChildren?

If you are facing this issue, fret not! You can implement a solution using HostListener.

Alternatively, another approach would be to utilize a MutationObserver.

Answer №2

Create

@ViewChild(FabricDropdownItemComponent) private items: QueryList<FabricDropdownItemComponent>

as a class property instead of using it as a constructor parameter

export class FabricDropdownComponent extends AbstractValueAccessor implements AfterViewInit {
  @ViewChild(FabricDropdownItemComponent) private items: QueryList<FabricDropdownItemComponent>
  constructor() {}
  ....
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transferring an ID as a parameter in the URL from a component to an Angular Material dialog

I've been struggling with performing edit operations within an Angular Material dialog. The issue I'm facing is the inability to extract a parameter from the URL, preventing me from carrying out edit operations. How can I pass the ID as a paramet ...

Firebase Functions Project encountering a "Cannot find module" error in VS Code

While working on a firebase functions project in Visual Studio Code, I encountered an issue inside the index.ts file. The imported modules were not being recognized even though autocomplete showed that the modules exist. When attempting to import them, I k ...

Set up Angular 2 Universal on your system

Update: I attempted to set up Angular 2 Universal following this guide, but encountered errors when executing the command below. Here is the command I ran: typings install node express body-parser serve-static dexpress-serve-static-core mime --global -- ...

404 Error: Unable to retrieve /api/posts

post.endpoint.ts class PostEndpoint implements Endpoint { public path = '/posts'; public router = Router(); private PostService = new PostService(); constructor() { this.initializeRoutes(); } private initializeRo ...

Looking to incorporate Functional Components in React using the package "@types/react" version "^18.0.17"? Learn how here!

With the removal of the children prop from React.FC type, what is the new approach for typing components? ...

What sets the do/tap operator apart from other observable operators?

Can anyone clarify the distinction in simple terms between the typical observable operators used for observing output and why do/tap appear to serve the same purpose? What is the reason for utilizing do/tap? ...

Angular - Error: Cannot read property 'publishLast' of undefined

My goal is to prevent multiple requests from being created when using the async pipe. I am facing an issue with a request to fetch a user from the API: getUser() { this._user = this.http.get<User>(environment.baseAPIUrl + 'user') ...

Tips for maintaining the selected state of a row using Typescript and the option tag

Here is the code for a dropdown: <div class="col-md-6"> <label for="inertiaStart" style="float: left; width: 17%;">Inertia Start</label> <select ng-model="selectedShiftChannel" style="float: left; width: 70%;height: 26 ...

The issue arises when IonViewDidLoad fails to retrieve data from the service class in Ionic after the apk file has been generated

Creating a form where users can input various information, including their country code selected from dropdowns. Upon submission, the data is displayed successfully when running in a browser. However, after building the apk file, the country codes fail to ...

Rendering a duplicate Angular 7 component in a router-outlet

Occasionally, this issue occurs (typically after launching the application in the morning). We utilize SwUpdate for app updates, and it seems that this problem aligns with the update schedule. The resulting HTML appears like this: <app-root _nghost-vr ...

Determine the general type of a different type

Imagine I have the following scenario : const sub = new Subject<{ id: string; value: string; }>(); Is there a method to obtain the generic type assigned to the Subject object? const item: ??? = { id: '1', value: 'value' }; Alth ...

Reduce the size of log messages in cypress

I am looking to shorten the cypress messages to a more concise string, for instance: Cypress log Transform to: -assert expected #buy-price-field to have value 17,169.00. Is there a way to achieve this? I have searched through the documentation but hav ...

Having trouble with TypeScript error in React with Material-UI when trying to set up tabs?

I have developed my own custom accordion component hook, but I am encountering the following error export default const Tabs: OverridableComponent<TabsTypeMap<{}, ExtendButtonBase<ButtonBaseTypeMap<{}, "button">>>> Check ...

Having trouble getting matSort to work in Angular 8 as it keeps returning an undefined error when trying

Having trouble getting the mat sort functionality to work on my table, as it keeps showing as undefined. I've tried various solutions from the documentation but nothing seems to be working for me. (I have removed ngIf, changed static to false, and tr ...

What is the best way to limit input to only numbers and special characters?

Here is the code snippet I am working with: <input style="text-align: right;font-size: 12px;" class='input' (keyup.enter)="sumTotal($event)" type="text" [ngModel]="field.value" (focusin)="focusin()" (focusout)="format()" (keyup.ente ...

Facing issues with Typescript imports for validatorjs?

Utilizing TypeScript with validator JS and encountering dependency issues: "dependencies": { "@types/validator": "^12.0.1", "validator": "^12.2.0" } Attempting to import isDividibleBy yields an error: import { isDivisibleBy } from "validato ...

Adjust the width and height of an Angular 5 iframe to match the dimensions of its content

I have multiple iframes and I am looking to dynamically adjust the height and width of each iframe based on its content. For instance, if the content within an iframe is sized at 1300x300, I want the iframe itself to also be 1300x300 when displayed on my ...

issues arise with tests following the transition from Angular 9 to Angular 10

Recently, I encountered an issue with my jest-tests after updating Angular from version 9 to 10. These tests were working perfectly fine before the update. Can someone guide me on how to resolve this issue? Below is one of the tests that is causing troubl ...

Incorporate dynamic HTML into Angular by adding CSS styling

Just starting out with Angular and have limited front-end experience. I'm feeling a bit lost on how to proceed. Currently, I have a mat-table with a static datasource (will connect to a database in the future), and I need to dynamically change the bac ...

Manifest error detected on Line 1, Column 1: Syntax issue found with AWS

I recently added PWA functionality to my Angular 8 app by running the command: $ ng add @angular/pwa --project my-app After setting up the necessary files for a PWA as described here, I encountered no errors during development. However, upon deploying t ...