Tag: parent/child

  • Set your parents free with Angular’s dependency injection

    Set your parents free with Angular’s dependency injection

    If you’re using a modular, component-driven architecture, one of the greatest challenges you will face is how to enable communication between parent and child components/directives. On the one hand, you want each component/directive to be independently focused on their purpose. On the other hand, you need them to work together as though they were built as a cohesive unit.

    Luckily for us, Angular provides everything you need for any type of situation!

    If your parent view contains the children, a common approach is to use the @Input() and @Output() decorators. To communicate with the parent, you can emit custom events through your children. To communicate with the children, you can pass properties that are bound to your parent.

    You can also use @ViewChild/@ViewChildren and @ContentChild/@ContentChildren to break down the parent/child walls. (I will discuss these powerful decorators in future articles.)

    If your parent view doesn’t contain the children directly or perhaps there is no relationship at all, you can use observables in a service to communicate data. That way, it doesn’t matter what the actual relationship is because anyone who injects the service can update and subscribe to the observables.

    Why use dependency injection to find a parent

    I created a tasking system that displays each task in a table row component. I also created button components to take actions on the table row. However, I didn’t want to include the button components in the table row component so they could be used regardless if they were in a table or not.

    The problem I faced was how to assign the button components a specific ID without having to pass the ID as an input over and over. For example, here is the HTML for the table row component that iterates over an array of tasks.

        <ces-table-row *ngFor="let task of tasks; let index = index;" [id]="task.id" [index]="index">
        <ces-table-column class="justify-center">
            <ces-view-pin [active]="task.pinned"></ces-view-pin>
            <ces-view-delete></ces-view-delete>
        </ces-table-column>
        <ces-table-column class="justify-center">{{ task.id }}</ces-table-column>
        <ces-table-column class="justify-center">
            <ces-task-menu-statuses [default]="task.status"></ces-task-menu-statuses>
        </ces-table-column>
        <ces-table-column>{{ task.dateUpdated | date: 'short' }}</ces-table-column>
        <ces-table-column>{{ task.title }}</ces-table-column>
        <ces-table-column>{{ task.notes }}</ces-table-column>
    </ces-table-row>
    

    The button components I mentioned use the selectors ‘ces-view-pin’ and ‘ces-view-delete’. I wanted these components to only be responsible for the state of the button interaction and to send a message to a service when they are pressed. That way, I could avoid having to repeat redundant code each time they are used and they would behave the same way no matter who was using them.

    Since the table row component is getting the task ID, I needed a way to navigate up the hierarchy to access it. Thankfully, Angular provides an easy way to search the hierarchy.

    How to search for a parent interface

    Step 1: Create an abstract class

    I started by creating an abstract class that contained the properties I wanted the button components to be able to access.

    export abstract class View {
      abstract id: number;
      abstract index: number;
    }
    

    Step 2: Implement the abstract class and add the provider

    The next step is to implement the abstract class and to provide a reference for anyone who tries to inject it.

    import { Component, forwardRef, Input } from '@angular/core';
    import { View } from 'projects/view/src/public-api';
    
    @Component({
      selector: 'ces-table-row',
      templateUrl: './table-row.component.html',
      styleUrls: ['./table-row.component.scss'],
      providers: [{
        provide: View, useExisting: forwardRef(() => TableRowComponent)
      }]
    })
    export class TableRowComponent implements View {
      @Input() id!: number;
      @Input() index: number = -1;
    
      constructor() { }
    }
    

    Step 3: Inject the interface into the child component/directive

    The last step is to inject the View Class using the @Optional() decorator to any child who needs to access the parent’s properties/methods that was defined in the abstract class.

    export class ViewDeleteComponent {
    
      constructor(
        protected _viewService: ViewService,
        @Optional() public view: View
      ) { }
    
      delete() {
        const message: ApiChange = {
          id: this.view.id,
          index: this.view.index,
          action: 'delete',
        }
    
        this._viewService.setChange(message);
      }
    }
    

    View this github gist if you want to see the code for all three components.

  • Celebrate independence with a completely self-sufficient Angular button that can hide any parent

    Celebrate independence with a completely self-sufficient Angular button that can hide any parent

    One of the most fun things about Angular – yes, I said fun AND I’m not being sarcastic – is coming up with ways to create components that are totally modularized and can perform functions with a plug-and-play attitude. For example, I created a button that can hide any parent just by including its component within the component you’re trying to hide.

    Start with a generic button component

    At the heart of every good component are the absolute bare-minimum basics. If you want to take advantage of a modular approach, make sure you are using building blocks that are flexible enough to build upon and that are loosely coupled so that they won’t become restrictive later on. In other words, you want to build components that you can confidently change because you know those changes will be isolated and not cause unintended ripple effects.

    So, the first step is to create a button component that will be the foundation of all of your other buttons. For example, below is the base button component I use for my blog’s website. It gives the developer an option to add whatever content they want for the button, as indicated by Angular’s “ng-content” tag.

    button.component.html

    <button [ngClass]="class" [disabled]="disabled">
      <ng-content></ng-content>
    </button>
    

    Add CSS to your button

    button.component.scss

      @import "src/assets/styles/variables.scss";
    
      button {
        font-size: inherit;
        background: inherit;
        color: inherit;
        text-align: center;
        padding: 0.5em 1.0em;
        border: 1px solid;
        border-radius: $border-radius;
      }
      button:hover {
        filter: brightness(125%);
        cursor:pointer;
      }
    
      button.large {
        padding: 1.0em 2.0em;
        font-size: 2.0em;
        border: $border;
        border-radius: $border-radius;
      }
    
      button.no-border {
        border:0;
      }
    
      button.no-padding {
        padding:0;
      }
    
      .icon {
        width: 3.0em;
        height: 3.0em;
      }
    
      button:disabled {
        opacity: 0.25;
      }
    

    The developer can disable the button by setting the disabled property to true. I added CSS to make the disabled button have an opacity of 0.25 so that it appears disabled as well.

    Instead of hardcoding a color for the hover state, I am setting a filter to make the brightness at 125% so that it can apply to any button no matter what color it is. I also added the cursor to change to the hand, or more precisely pointer, upon hover since that’s not a default behavior.

    The developer can also choose to not have a border or padding.

    button.component.ts

      import { Component, Input } from '@angular/core';
    
      @Component({
        selector: 'ces-button',
        templateUrl: './button.component.html',
        styleUrls: [
          './button.component.scss'
        ]
      })
      
      export class ButtonComponent {
        @Input() class:string;
        @Input() disabled:boolean;
       }
    

    Next, create the button that can hide its parent

    I added inputs for the user to pass the label and opt for the default icon if desired. (This is useful if the developer wants to use the styles that are applied to the button, e.g., font size, padding, icon size, etc.)

    button-hide-parent.component.html

      <ces-button type="button" class="no-border no-padding" (click)="onClose()">
        <span *ngIf="label" class="label">{{ label }}</span>
        <ces-svg-x-circle class="button-close-icon"></ces-svg-x-circle>
      </ces-button>
    

    button-hide-parent.component.ts

      import { Component, Input, Renderer2, ElementRef, Output, EventEmitter } from '@angular/core';
    
      @Component({
        selector: 'ces-button-hide-parent',
        templateUrl: './button-hide-parent.component.html',
        styleUrls: ['./button-hide-parent.component.scss']
      })
      export class ButtonHideParentComponent  {
        @Input() icon: string;
        @Input() label:string;
        @Output() hidden: EventEmitter<any> = new EventEmitter();
    
        constructor(
          private renderer:Renderer2,
          private elem:ElementRef
        ) { }
    
      onClose() {
        const parent = this.renderer.selectRootElement(this.elem.nativeElement.parentNode);
        this.renderer.setStyle(parent, 'display', 'none')
        this.hidden.emit();
      }
    }
    

    By creating an instance of ElementRef, I’m able to easily obtain a reference to the component and therefore get the component’s parent without having to query the DOM. By using Renderer2, I’m able to safely target the parent component and apply the style “display:none.”

    I also added an EventEmitter just in case the parent needs to know that the parent was hidden.

    Use the new button

    To use the new button, include the selector in the HTML template of the component you would like to hide. No additional code is required to make it work.

    Note: You can specify another click event handler on the button in case you want to add another function in the parent’s typescript file. By doing so, clicking the button will invoke both the child’s and parent’s methods.