1

I try to create my own structure directive that can create some DOM structure (button group in my case). I want to create custom tags for wrapper and child and apply custom classes.

I stuck on moment - Output event. I finished directive. It's working. (I make DOM manipulation inside this directive). But now I can't emit event from this directive 9I found answers that it's because events should be static. like - (click)="some()") But how I can do it if my child's created dynamically? (For now I use AddEventListener).

and have this error: Event binding onBtnClick not emitted by any directive on an embedded template

Thanks for any information.

Here is my component code:

import {Component, Injectable, Input, Output, EventEmitter, OnInit, ElementRef} from '@angular/core';

import { BtnGroupChildStructure } from './button_group.directive';
import { Config } from 'appConfig';
/* ------- !Config  ---------*/

const MODULE_NAME: string = 'button_group';
const MODULE_PATH: string = `${Config.getProdFolderName()}/shared/components/${MODULE_NAME}`;

@Component({
    selector : 'cgm_button_group',
    template : `<template *btnGroupChildStruct="config" 
                (onBtnClick)="t_handleBtnClick($event)"></template>`,
    host : {'class': 'button-group-box'},
    directives : [
        BtnGroupChildStructure
    ]
})
@Injectable()
export class ButtonGroupComponent implements OnInit {

    private _config: Object;

    @Input() config(val: Object) {
        console.log(val);
        Object.assign(this._config, val);
        console.log(this._config);
    }

    @Output onBtnClick = new EventEmitter<string>();

    ngOnInit() {

    }

    private t_handleBtnClick(buttonName: string): void {
        this.onBtnClick.emit(buttonName);
    }
}

Here is Directive code

import { Directive, Input, 
    ViewContainerRef, ElementRef, TemplateRef, OnInit, Output, EventEmitter } from '@angular/core'

@Directive({ selector : '[btnGroupChildStruct]'})
export class BtnGroupChildStructure implements OnInit {

    private _hostEl;
    private _config: Object = {};

    @Input() set btnGroupChildStruct(val: Object) {
        console.log(val);
        Object.assign(this._config, val);
    }

    @Output() onBtnClick = new EventEmitter<string>();

    constructor(
        private _templateRef: TemplateRef<any>,
        private _viewContainer: ViewContainerRef,
        private _elementRef: ElementRef
    ) {
        this._hostEl = this._elementRef.nativeElement.parentElement;

    }

    ngOnInit() {
        //console.log(this._templateRef);
        this._generateChilds();
        this._hostEl.appendChild(this._generateGroup());
        //this._viewContainer.createEmbeddedView(this._templateRef);
    }

    /**
     * _generateChilds -> generate childs structure
     * @returns {DocumentFragment}
     * @private
     */
    private _generateChilds(): DocumentFragment {
        let fragment = document.createDocumentFragment();

        if (this._config.hasOwnProperty('buttonNamesList')) {
            this._config.buttonNamesList.forEach((buttonName) => {
                let item = document.createElement(this._config.tagChild);
                this._addClasses(item, this._config.childClasses);
                item.textContent = buttonName;
                this._addClickHandler(item);
                fragment.appendChild(item);
            })
        } else {
            throw new Error('Button group Directive. _generateChilds. Can"t get _config buttonNamesList prop');
        }

        return fragment;

    }

    /**
     * _generateGroup -> generate button group
     * @returns {HTMLElement}
     * @private
     */
    private _generateGroup(): HTMLElement {
        let wrapperTag;
        if (this._config.hasOwnProperty('tagWrapper')) {
            wrapperTag = document.createElement(this._config.tagWrapper);
            this._addClasses(wrapperTag, this._config.wrapperClasses);
            wrapperTag.appendChild(this._generateChilds());
        } else {
            throw new Error('Button group Directive. _generateGroup. Can"t get _config tagWrapper prop');
        }

        return wrapperTag;
    }

    /**
     * _addClasses -> Add classes to el
     * @param el (HTMLElement) -> handled Element
     * @param classesStr (String) -> string of classes
     * @private
     */
    private _addClasses(el: HTMLElement, classesStr: string): void {
        if (!el || !classesStr) throw new Error('_addClasses. Missed one of parameter');
        el.className = classesStr
    }

    private _addClickHandler(el: HTMLElement): void {
        if (!el) throw new Error('_addClickHandler. Non exist element');
        el.addEventListener('click', (el) => {
            this.onBtnClick.emit(el.textContent);
        })
    }

}
Maximilian Riegler
  • 22,720
  • 4
  • 62
  • 71
Velidan
  • 5,526
  • 10
  • 48
  • 86
  • 4
    `@Output` needs to be `@Output()` – Günter Zöchbauer Jul 07 '16 at 11:29
  • Hi Gunter. Correct. I fix it but got the same error. – Velidan Jul 07 '16 at 12:07
  • 1
    If you use a structural directive on a ` – Günter Zöchbauer Jul 07 '16 at 12:58
  • Hi Gunter again. When I try to use some 'real' tag (section- for example) instead template tag, tt's not works because my dynamically created elements is not render inside this tag. So that's why I used template. Because I don't know how I can place dynamically created elements inside this tag. I tried to use this _//this._viewContainer.createEmbeddedView(this._templateRef);_ but I don't know How I can create reference to my dynamic elements. – Velidan Jul 08 '16 at 12:44
  • An example with Plunker http://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 – Günter Zöchbauer Jul 08 '16 at 16:21
  • Thanks Gunter so much for your help. – Velidan Jul 12 '16 at 13:34
  • Looks like it's dynamic component builder stuff, but I just want to create few element dynamically and insert in correctly inside my component by ng2 way (I don;t want to use _append_ or innerHTML). Is it possible? (like with _this._viewContainer.createEmbeddedView_) – Velidan Jul 13 '16 at 08:07
  • Can you please explain a bit more what you actually try to accomplish? Have you tried to use ` – Günter Zöchbauer Jul 16 '16 at 11:01
  • actually I tried to use some component where I create dynamically set of html elements. And after that I want to append this elements inside my host element with all angular rules (I want to bind some event handling or expressions. But I don't know how it can be applied to dynamic elements) From start I use *template* tag because this tag is like _non-real_ and all dynamic childs can be easy append inside. But I have issue with ng2 bindings event. When I switch to "real" tag _div_ I don't know how I can apped my elements inside this tag with all ng2 rules. – Velidan Jul 20 '16 at 08:10
  • I saw that I can paste some html structure inside host with _this._viewContainer.createEmbeddedView(this._templateRef)_ Lets imagine that I have few dynamically created elements. How I can paste it inside my component host tag an how i can apply all angular features (event handling/expressions etc). – Velidan Jul 20 '16 at 08:12
  • Angular2 doesn't process bindings of dynamically added HTML. Everything, also the ` – Günter Zöchbauer Jul 20 '16 at 08:13
  • Thanks so much Gunter! – Velidan Jul 21 '16 at 15:52

0 Answers0