How do I create my own component lifecycle callback associated with another component in Angular?

Viewed 144

I'm having difficulty getting the hang of Angular, and additionally Promises. I'm trying to create another layer of wrapper for the angular-datatables to add some generic customization, and then let the base component know it's complete and give access to that instance variable. So similar to ngAfterViewInit but "parentNgAfterViewInit" - which I'll name as dtAfterInit.

app.component.ts (level 1/ base)

export class AppComponent implements OnInit, OnDestroy {
  ngOnInit(): void {
   this.http.get<any[]>("./assets/data/data.json").subscribe(datas => {
   this.datas = datas;
   this.dtTrigger.next();
   });
  });
 }

  ngOnDestroy(): void {
    this.dtTrigger.unsubscribe();
  }

  dtAfterInit(dtInstance: DataTables.Api): void {
    //@todo
  }
}

app.component.html (level 1/ base)

  <datatable
    [dtOptions]="dtOptions"
    [dtTrigger]="dtTrigger"
    paginationLength="true"
    filter="true"
    tableClass="table table-striped table-bordered table-hover"
    width="100%"
  >
    <thead>
     <!-- THEAD -->
    </thead>
    <tbody>
     <!-- TBODY -->
    </tbody>
  </datatable>

datatable.component.ts (level 2)

@Component({
  selector: "datatable",
  template: `
    <table
      datatable
      [dtOptions]="dtOptions"
      [dtTrigger]="dtTrigger2"
      class="dataTable responsive {{ tableClass }}"
      width="{{ width }}"
    >
      <ng-content></ng-content>
    </table>
  `
})
export class DatatableComponent implements OnInit, OnDestroy {
  @ViewChild(DataTableDirective, { static: false })
  datatableElement: DataTableDirective;

  /** <DataTables Inputs> */
  @Input() public dtOptions: DataTables.Settings = {};
  @Input() public dtTrigger: Subject<any> = new Subject();
  /** </DataTables Inputs> */

  private dtTrigger2: Subject<any> = new Subject();

      ngOnInit(): void {
        if (this.dtTrigger) {
          this.dtTrigger.subscribe(() => {
            this.dtTrigger2.next();
            this.datatableElement.dtInstance.then((dtInstance: DataTables.Api) => {
              //window["test"] = dtInstance; //Give this instance over to the AppComponent
              //@todo AppComponent.dtAfterInit(dtInstance)
            });
          });
        }
      }
    
      ngOnDestroy(): void {
        this.dtTrigger2.unsubscribe();
      }
    
      /** Attempt #2, but
       *  This doesn't quite work because it isn't guaranteed that (the HTTP request is completed prior to this)
       *  this.dtTrigger.next() got called yet, so this.datatableElement.dtInstance could be null
      ngAfterViewInit(): void {
        this.datatableElement.dtInstance.then((dtInstance: DataTables.Api) => {
          window["test"] = dtInstance; //Give this instance over to the AppComponent
          //@todo AppComponent.dtAfterInit(dtInstance)
        });
      } */
  1. How can I let AppComponent know dtInstance is initialized, and give instance to that component?

In the @todo, the dtInstance is successfully initialized. Now I would like to pass this variable from DatatableComponent back to AppComponent, and for it to do any additional individual component level event binding from this point of time (after it has been initialized), or just to be able to access the Datatables API directly.

  1. How can I do this more "Angular-ly"?

In the example above, I was trying to create two / nested subjects, so that when AppComponent triggers next, it'll trigger the DatatableComponent trigger, which then trigger the DataTableDirective (angular-datatable wrapper library), so we can do our dtAfterInit callback here. But I imagine this is very ugly and not very "Angular", and additionally, I used the window["test"] = dtInstance to make dtInstance a global variable so it can accessible by AppComponent to better illustrate what I'm trying to do.

Is there an event that will guarantee http.get() is completed and as such, the dt is also initialized? I have tried ngAfterViewInit, but I don't believe this works as this.dtTrigger.next() isn't triggered yet, so dtInstance is null. Would the best approach here to be to modify the angular-datatables wrapper directly?

angular-datatables.directive.ts (level 3)

@Directive({
  selector: '[datatable]'
})
export class DataTableDirective implements OnDestroy, OnInit {
  dtInstance: Promise<DataTables.Api>;
  ngOnInit(): void {
    if (this.dtTrigger) {
      this.dtTrigger.subscribe(() => {
        this.displayTable();
      });
    } else {
      this.displayTable();
    }
  }
  private displayTable(): void {
    this.dtInstance = new Promise((resolve, reject) => {
      Promise.resolve(this.dtOptions).then(dtOptions => {
        // Using setTimeout as a "hack" to be "part" of NgZone
        setTimeout(() => {
          this.dt = $(this.el.nativeElement).DataTable(dtOptions);
          resolve(this.dt);
        });
      });
    });
  /* @attempt#3, I'm thinking maybe directly modifying this library be the best way? If there isn't an event I can use, or to avoid having the two trigger to just one
     ex. Promise.resolve(this.dtInstance).then(
              //@todo AppComponent.dtAfterInit(dtInstance)
         );
  */
  }

Here's a link of a sandbox for the full code of my attempt.

0 Answers
Related