Adding Ng directives to Dynamically Created Buttons

Viewed 26

The goal of this code is to dynamically generate ng-containers that generate buttons with classes that either is active or inactive depending on a user's permissions list. Due to limitations currently, I can not retrieve the permissions list and need to create the buttons before the permissions list is pulled from the user token.

I am trying to create buttons on the OnInit() for the component, which has a series of ng-templates that are chosen between via ngIf directives according to the users permissions map which is stored locally.

I have looked around and understand that directives need to be compiled, but was wondering if there are any tricks or help I can be pointed to as my search has only pulled up common references to it not being possible or tricks that require already generating part of the html.

component.ts

import { KeyValuePipe } from '@angular/common';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Route } from '@angular/router';
import { createPopper } from '@popperjs/core';
import * as libs from '../../../../assets/jsonFiles/libs.json';

@Component({
  selector: 'app-libs-view',
  encapsulation: ViewEncapsulation.None,
  templateUrl: './libs-view.component.html',
  styleUrls: ['./libs-view.component.css']
})
export class LibsViewComponent implements OnInit {

  hasLibraryAccess = new Map<String, boolean>();
  constructor(route: ActivatedRoute) {  }

  ngOnInit(): void {
    this.formatLibraryButtons()
  }

  formatLibraryButtons() {
    let btnLayoutHlpr = 0;

    // hard coded for now to test the original 12 buttons
    // should read json and get each library, and create a button correctly for it
    for (let eachLib in Libs) {
      let lib = Libs[eachLib].Name
      console.log(lib)
      // https://www.typescriptlang.org/docs/handbook/dom-manipulation.html
      if (btnLayoutHlpr < 2) {
        const btnGrp = document.getElementById("btnGrp1");
        //this.createButton(btnGrp!, lib)
        ++btnLayoutHlpr;
      } else if (btnLayoutHlpr < 4) {
        const btnGrp = document.getElementById("btnGrp2");
        //this.createButton(btnGrp!, lib)
        ++btnLayoutHlpr
      } else if (btnLayoutHlpr < 6) {
        const btnGrp = document.getElementById("btnGrp3");
        //this.createButton(btnGrp!, lib)
        ++btnLayoutHlpr
      } else if (btnLayoutHlpr < 9) {
        const btnGrp = document.getElementById("btnGrp4");
        //this.createButton(btnGrp!, lib)
        ++btnLayoutHlpr
      } else if (btnLayoutHlpr < 12) {
        const btnGrp = document.getElementById("btnGrp5");
        //this.createButton(btnGrp!, lib)
        ++btnLayoutHlpr
      } else {
        //console.log("check")
        ++btnLayoutHlpr
      }

     // setting all to true for dev purposes
      this.hasLibraryAccess.set(lib, true); all library names must start lowercase or angular's auto-sort method messes up btn placement

    }
    console.log("Button creation complete")
    console.log("Library Access Map Created")
 }

  createButton(btnGrp : Element, lib: string) {
    // create ng-content div to hold logic for if library is accessible
    const ngCont = document.createElement("ng-container")
    
    // create ngIf for checking if customer has access to library
    const ngIf = document.createElement("div")
    //ngIf add ngIf to the div such that ngIf=libraryAccess.value | then libAccessTrue else libAccessFalse
    ngCont.appendChild(ngIf)
    
   // create first template to add library access button
    const ngTemp = document.createElement("ng-template")
    //ngTemp.add add #LibraryAccessTrue

    // create div wrapper for btn
    const divWrapper = document.createElement("div")
    divWrapper.style.marginLeft = "1 px";

    // create accessible btn and add it to  divWrapper and add divWrp to template
    const libBtn = document.createElement("a");
    libBtn.classList.add('hexagon', 'd-flex', 'align-self-center');
    libBtn.textContent = lib;
    divWrapper.appendChild(libBtn)
    ngTemp.appendChild(divWrapper)
    
    // add first template for accessible library button to ng-content
    ngCont.appendChild(ngTemp);

    // create second template to add library access button
    const ngTemp2 = document.createElement("ng-template")
    //ngTemp.add add #LibraryAccessTrue

    // create second div wrapper for btn
    const divWrapper2 = document.createElement("div")
    divWrapper2.style.marginLeft = "1 px";

    // create disabled btn and add it to its divWrapper then to template
    const libBtn2 = document.createElement("a");
    libBtn2.classList.add('hexagon-disabled', 'd-flex', 'align-self-center');
    libBtn2.textContent = lib;
    divWrapper2.appendChild(libBtn2)
    ngTemp2.appendChild(divWrapper2)
    
    // add second template for disabled library button to ng-content
    ngCont.appendChild(ngTemp2);

    // add the entire ng-content div to the button group
    btnGrp?.appendChild(ngCont);
  }

}

component.html

<div class="d-flex">
  <div class="container parent-hexagon">
    <div class="d-inline-block" style="margin-left: 8%; width: 80%">

      <div id="btnGrp1" class="btn-toolbar">
        <div *ngFor="let libraryAccess of hasLibraryAccess | keyvalue">

        </div>
      </div>

      <div id="btnGrp2" class="btn-toolbar" style="margin-left: 12%">
        <div *ngFor="let libraryAccess of hasLibraryAccess | keyvalue">

        </div>
      </div>

      <div id="btnGrp3" class="btn-toolbar">
        <div *ngFor="let libraryAccess of hasLibraryAccess | keyvalue">

        </div>
      </div>

      <div id="btnGrp4" class="btn-toolbar" style="margin-left: 12%">
        <div *ngFor="let libraryAccess of hasLibraryAccess | keyvalue">

        </div>
      </div>

      <div id="btnGrp5" class="btn-toolbar" style="margin-left: 24.25%">
        <div *ngFor="let libraryAccess of hasLibraryAccess | keyvalue">
          
      </div>
    </div>

  </div>
 </div>
</div>

component.css

.parent-hexagon {
  max-width: 90%;
  width: 40vw;
  height: 42vh;
  background: green;
  position: absolute;
  z-index: 0;
}

  .parent-hexagon::before {
    content: "";
    position: absolute;
    top: -20vh;
    left: 0;
    width: 0;
    height: 0;
    border-left: 20vw solid transparent;
    border-right: 20vw solid transparent;
    border-bottom: 20vh solid green;
  }

  .parent-hexagon::after {
    content: "";
    position: absolute;
    bottom: -25vh;
    left: 0;
    width: 0;
    height: 0;
    border-left: 20vw solid transparent;
    border-right: 20vw solid transparent;
    border-top: 25vh solid green;
  }

.hexagon {
  width: 7.5vw;
  min-width: 100%;
  max-width: 100%;
  height: 8.5vh;
  color: black;
  font-size: 90%;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  background: #bfe8f7;
  position: relative;
  z-index: 1;
  margin-bottom: 28.865px;
}

  .hexagon::before {
    content: "";
    position: absolute;
    top: -28.8675px;
    left: 0;
    width: 0;
    height: 0;
    border-left: 3.75vw solid transparent;
    border-right: 3.75vw solid transparent;
    border-bottom: 28.8675px solid #bfe8f7;
  }

  .hexagon::after {
    content: "";
    position: absolute;
    bottom: -28.8675px;
    left: 0;
    width: 0;
    height: 0;
    border-left: 3.75vw solid transparent;
    border-right: 3.75vw solid transparent;
    border-top: 28.8675px solid #bfe8f7;
  }

  .hexagon:hover {
    background: #7fd1ef;
    z-index: 2;
    transform: scale(1.15)
  }

    .hexagon:hover:before {
      content: "";
      position: absolute;
      top: -28.8675px;
      left: 0;
      width: 0;
      height: 0;
      border-left: 3.75vw solid transparent;
      border-right: 3.75vw solid transparent;
      border-bottom: 29.8675px solid #7fd1ef;
    }

    .hexagon:hover:after {
      content: "";
      position: absolute;
      bottom: -28.8675px;
      left: 0;
      width: 0;
      height: 0;
      border-left: 3.75vw solid transparent;
      border-right: 3.75vw solid transparent;
      border-top: 29.8675px solid #7fd1ef;
    }

.hexagon-disabled {
  width: 7.5vw;
  height: 8.5vh;
  color: black;
  font-size: 90%;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  background: #caced0;
  position: relative;
  z-index: 1;
  margin-bottom: 28.865px;
}

  .hexagon-disabled::before {
    content: "";
    position: absolute;
    top: -28.8675px;
    left: 0;
    width: 0;
    height: 0;
    border-left: 3.75vw solid transparent;
    border-right: 3.75vw solid transparent;
    border-bottom: 28.8675px solid #caced0;
  }

  .hexagon-disabled::after {
    content: "";
    position: absolute;
    bottom: -28.8675px;
    left: 0;
    width: 0;
    height: 0;
    border-left: 3.75vw solid transparent;
    border-right: 3.75vw solid transparent;
    border-top: 28.8675px solid #caced0;
  }


  .hexagon-disabled:hover {
    background: #caced0;
    z-index: 2;
    transform: scale(1.15)
  }

    .hexagon-disabled:hover:before {
      content: "";
      position: absolute;
      top: -28.8675px;
      left: 0;
      width: 0;
      height: 0;
      border-left: 3.75vw solid transparent;
      border-right: 3.75vw solid transparent;
      border-bottom: 29.8675px solid #caced0;
    }

    .hexagon-disabled:hover:after {
      content: "";
      position: absolute;
      bottom: -28.8675px;
      left: 0;
      width: 0;
      height: 0;
      border-left: 3.75vw solid transparent;
      border-right: 3.75vw solid transparent;
      border-top: 29.8675px solid #caced0;
    }

As shown above, the issue is the html is pretty sparse beyond setting up the button groups and the ngFor directives to iterate the libraryAccessPermissions map that tells the ng-containers being dynamically created which ng-template to show. If I could get the permissions, I would forgo the ngIf and just generate the buttons with css classes dependent on their permissions. But since they won't, for project reasons, be given till after the components Init(), I have to create templates and a permission map to track and properly show buttons.

Any help on how to generate these buttons would be welcome and grateful for.

1 Answers

no,no,no,no...

We are using Angular. Angular is related to use variables in .ts and show data in the .html. So you need "think in variables"

Imagine a variable like

groupButtons=[
   [{text:"one",level:0},{text:"two",level:1}],
   [{text:"three",level:0},{text:"four",level:0}],
   [{text:"five",level:99}]
]
user={name:"USER 1",level:99} //I imagine a variable "user"

You can easy use *ngFor and *ngIf (see the docs)

 to write
<div class="d-flex">
  <div class="container parent-hexagon">
    <div class="d-inline-block" style="margin-left: 8%; width: 80%">
       <!--iterate over group-->
       <!--see how use [style.margin-left] to add the margin-left 
           to all the groups else the first
           BTW, Why not use justify-content:space-between?
       -->
       <div *ngFor="group of groupButtons;let first=first" class="btn-toolbar"
          [style.margin-left]="first?null:'12%'">

            <!--the second loop-->
            <ng-container *ngFor="button of group">
             
              <div class="wrapper">
                 <!--the "a" is disabled if user.level<button.level"-->
                 <a [class.hexagon-disabled]="user.level<button.level"

                    <!--only execute "doSomething" if user.level>=button.level-->

                    (click)="user.level>=button.level && doSomething(button)"> 
                    {{button.text}}
                 </a>
              </div>
            </ng-container>
       </div>
    </div>
  </div>
</div>

See that you not create buttons in the javascript way, else allow Angular show the buttons (and add classes) using variables

NOTE: It's only an example

Related