title: What approach should I use when wrapping components from a UX\UI library
body: <p>I work in a big company that develops a Web App using Angular. Recently we have come across <a href="https://www.primefaces.org/primeng/#/" rel="nofollow noreferrer">PrimeNG</a> which is a UX/UI library.
  We would like to use the components this library offers.</p>
  <p>After testing a few things we came to the understanding that wrapping the components and sending some sort of configuration in our own &quot;language&quot; and translating it to PrimeNG language is the approach we want to pursue.</p>
  <p>My question is: What is the correct approach to implement this?</p>
  <p>Me and my peers thought of 3 options:</p>
  <ol>
  <li><p>Send Inputs to the wrapping component like so :
  <code>&lt;p-button-wrapper [size]=&quot;large&quot; [type]=&quot;primary&quot;&gt;&lt;/p-button-wrapper&gt;</code>
  The problem is that following this approach, we will have to permeate inputs to all the components that use the smaller wrapper component such as button (Which leads to components oozing inputs to components oozing more input, etc...)</p>
  </li>
  <li><p>We found a way by using Injectable settings, using angulars' hierarchical dependency injection mechanism so that each CSS Component (layout) / Angular Component could determine what are the settings that they would like to use for the specific components that they hold</p>
  </li>
  </ol>
  <pre><code>@Injectable()
  export class ButtonSettings {
     type: ButtonType = ButtonType.normal;
     size: ButtonSize = ButtonSize.medium;
     isRounded: boolean;
  }
  </code></pre>
  <p>Now we can override the settings in each level of the hierarchy as we want for example:</p>
  <pre><code>@Directive({
      selector: '.header',
      providers:[
          {provide: ButtonSettings},
          {provide: ButtonSettings, useExisting: HeaderButtonSettings}]
  })
  export class HeaderDirective {
      constructor(private buttonSettings: HeaderButtonSettings, private elementRef: ElementRef) {
      }
  
      ngOnInit() {
          this.buttonSettings.name = &quot; header&quot;;
          this.buttonSettings.isRounded = true;
          this.buttonSettings.size = ButtonSize.medium;
          this.buttonSettings.type = ButtonType.normal;
  
          if (this.elementRef.nativeElement.classList.contains(&quot;small&quot;)) {
              this.buttonSettings.size = ButtonSize.small;
              this.buttonSettings.isRounded = false;
          }
  
          if (this.elementRef.nativeElement.classList.contains(&quot;with-shadow&quot;)) {
              this.buttonSettings.type = ButtonType.outlined;
              this.buttonSettings.size = ButtonSize.small;
          }
      }
  }
  </code></pre>
  <p>This introduces us to the usage of the &quot;ElementRef&quot; angular service since we want different behaviors for different types of the layout that is being described in the directive.</p>
  <p>Finally, the wrapper component will simply inject the settings and &quot;translate&quot; them as described above.</p>
  <pre><code>@Component({
      selector: 'p-button-wrapper',
      templateUrl: 'p-button-wrapper.html',
      styleUrls: ['p-button-wrapper.scss']
  })
  export class PButtonWrapper implements OnInit {
      protected  size: ButtonSize;
      protected  type: ButtonType;
      protected isRounded: boolean;
  
      constructor(private buttonSettings : ButtonSettings) {
      }
  
      ngOnInit() {
          this.size = this.buttonSettings.size;
          this.type = this.buttonSettings.type;
          this.isRounded = this.buttonSettings.isRounded;
      }
  
      get sizeClassText(){
          if(this.size === ButtonSize.large){
              return 'p-button-lg'
          }
          if(this.size === ButtonSize.medium){
              return 'p-button'
          }
          if(this.size === ButtonSize.small){
              return 'p-button-sm'
          }
      }
  
      get typeClassText(){
          if(this.type === ButtonType.outlined){
              return 'p-button-outlined'
          }
          if(this.type === ButtonType.text){
              return 'p-button-text'
          }
          if(this.type === ButtonType.link){
              return 'p-button-link'
          }
      }
  
      get severityClassText(){
          if(this.severity == ButtonSeverity.secondary){
              return 'p-button-secondary'
          }
          if(this.severity == ButtonSeverity.info){
              return 'p-button-info'
          }
          if(this.severity == ButtonSeverity.success){
              return 'p-button-success'
          }
          if(this.severity == ButtonSeverity.warning){
              return 'p-button-warning'
          }
          if(this.severity == ButtonSeverity.help){
              return 'p-button-help'
          }
          if(this.severity == ButtonSeverity.error){
              return 'p-button-danger'
          }
      }
  }
  </code></pre>
  <p>another use case that is possible is having a layout &quot;C&quot; behave differently when in layout &quot;A&quot; or layout &quot;B&quot; and since we don't want is to &quot;lookup&quot; the hierarchy for a layout to determine the behavior of the child components as presented in the next code segment, this approach would make use making many Settings classes and writing a lot more code.</p>
  <pre><code>@Directive({
      selector: '.card',
      providers:[
          {provide: ButtonSettingsTest},
          {provide: ButtonSettings, useExisting: CardButtonSettings}
      ]
  })
  export class AdminHeaderTopDirectiveTest {
      constructor(private buttonSettings: CardButtonSettings, private elementRef: ElementRef) {
      }
  
      ngOnInit() {
          this.buttonSettings.type = ButtonType.normal;
          this.buttonSettings.isRounded = true;
  
          let header = this.elementRef.nativeElement.closest(&quot;.header&quot;);
  
          if(header.classList.contains(&quot;with-shadow&quot;)) {
              this.buttonSettings.isRounded = false;
          }
      }
  }
  </code></pre>
  <ol start="3">
  <li>Our third thought approach is using CSS custom properties, a feature that is not intended for passing on inputs and defining API but we found it quite useful.</li>
  </ol>
  <p>SCSS enabled us to define hierarchical structure easily (where the injectable settings make us write a lot of code).
  CSS also has built-in precedence logic which is useful for us.
  This approach looks something like this :</p>
  <pre><code>// layouts.scss
  
  $p-button-size-small: 2;
  $p-button-size-medium: 1;
  $p-button-size-large: 0;
  
  $p-button-type-normal: 0;
  $p-button-type-outlined: 1;
  $p-button-type-text: 2;
  $p-button-type-link: 3;
  
  $p-true: 1;
  $p-false: 0;
  
  @function get-prop-name($name) {
    @return &quot;--&quot; + $name;
  }
  
  @mixin ce-property($name, $value) {
    #{get-prop-name($name)}: $value;
  }
  
  .header {
      @include ce-property(&quot;p-button-size&quot;, $p-button-size-medium);
      @include ce-property(&quot;p-button-type&quot;, $p-button-type-normal);
      @include ce-property(&quot;p-button-is-rounded&quot;, $p-true);
  
      &amp;.small {
        @include ce-property(&quot;p-button-size&quot;, $p-button-size-small);
        @include ce-property(&quot;p-button-is-rounded&quot;, $p-false);
      }
  
      &amp;.with-shadow {
        @include ce-property(&quot;p-button-type&quot;, $p-button-type-outlined);
        @include ce-property(&quot;p-button-size&quot;, $p-button-size-small);
  
        .header-top {
          @include ce-property(&quot;p-button-is-rounded&quot;, $p-false);
        }
      }
  
      .header-top {
        @include ce-property(&quot;p-button-type&quot;, $p-button-type-normal);
        @include ce-property(&quot;p-button-is-rounded&quot;, $p-true);
      }
  }
  
  .card {
      @include ce-property(&quot;p-button-is-rounded&quot;, $p-true);
  
      .header.with-shadow &amp; {
          @include ce-property(&quot;p-button-is-rounded&quot;, $p-false);
      }
  }
  </code></pre>
  <p>Then we can read these CSS properties inside the wrapper component and &quot;translate&quot; them as described above</p>
  <pre><code>@Component({
      selector: 'p-button-wrapper',
      templateUrl: 'p-button-wrapper.html',
      styleUrls: ['p-button-wrapper.scss']
  })
  export class PButtonWrapper implements OnInit {
      protected  size: ButtonSize;
      protected  type: ButtonType;
      protected isRounded: boolean;
  
      constructor(private elemRef: ElementRef) {
      }
  
      ngOnInit(): void {
          const style = window.getComputedStyle(this.elemRef.nativeElement);
  
          // // Note : Not supported by Firefox, IE, Safari, FF Android, Safari on IOS
          // const style =  this.elemRef.nativeElement.computedStyleMap();
  
          const size = parseInt(style.getPropertyValue(&quot;--p-button-size&quot;));
          const type = parseInt(style.getPropertyValue(&quot;--p-button-type&quot;));
          const isRounded = parseInt(style.getPropertyValue(&quot;--p-button-is-rounded&quot;));
  
          this.size = size || ButtonSize.large;
          this.type = type || ButtonType.normal;
          this.isRounded = isRounded == 1 || false;
      }
  
      get sizeClassText(){
          if(this.size === ButtonSize.large){
              return 'p-button-lg'
          }
          if(this.size === ButtonSize.medium){
              return 'p-button'
          }
          if(this.size === ButtonSize.small){
              return 'p-button-sm'
          }
      }
  
      get typeClassText(){
          if(this.type === ButtonType.outlined){
              return 'p-button-outlined'
          }
          if(this.type === ButtonType.text){
              return 'p-button-text'
          }
          if(this.type === ButtonType.link){
              return 'p-button-link'
          }
      }
  
      get severityClassText(){
          if(this.severity == ButtonSeverity.secondary){
              return 'p-button-secondary'
          }
          if(this.severity == ButtonSeverity.info){
              return 'p-button-info'
          }
          if(this.severity == ButtonSeverity.success){
              return 'p-button-success'
          }
          if(this.severity == ButtonSeverity.warning){
              return 'p-button-warning'
          }
          if(this.severity == ButtonSeverity.help){
              return 'p-button-help'
          }
          if(this.severity == ButtonSeverity.error){
              return 'p-button-danger'
          }
      }
  }
  </code></pre>
  <p>Here we need to use the <code>window.getComputedStyle(this.elemRef.nativeElement)</code> function to get a list of property keys and then use <code>style.getPropertyValue(&quot;--ce-button-size&quot;)</code> to get the value.</p>
  <p>The big PROS for this approach are clear I assume but the main CON is that the API is not typed and for someone to use this component he would have to look inside the components code.</p>
  <p>What are some best practices to decide which approach is the best? We need high flexibility regarding hierarchical structuring and our schedule is somewhat tight.</p>