ngx-render-if-endpoints

Control the includes a template in the DOM tree using custom endpoints and directive for Angular 8+

Usage no npm install needed!

<script type="module">
  import ngxRenderIfEndpoints from 'https://cdn.skypack.dev/ngx-render-if-endpoints';
</script>

README

NgxRenderIfEndpoints

MIT Angular8+

About

Control the includes a template in the DOM tree using custom endpoints and directive for Angular 8+.

  1. Define the 'debounce time resize event' of re-render the view after changing the window size, groups, and endpoints by viewport for them in the module.

  2. Add a directive to the DOM of an element and specify endpoints to display the element.

  3. Listen for events from a directive: initialization, rendering, hidden, destroy.

What is it?

Building adaptive user interfaces can be difficult, and some components can:

  • consume a lot of resources or slow down application performance depending on the power of the device, the number of requests to the server or styling features

  • be difficult to style for different browsers and their versions

  • contain unnecessary HTML that needs to be hidden depending on the width of the viewport, etc

Often, everything is solved by a bunch of if blocks «if», removing "heavy" components from the DOM, using the old css standard for cross-browser compatibility, many additional BEM classes that control the display of HTML blocks depending on the width of the viewport.

Sometimes that’s not enough, and we want to control every step during whole life cycle of the application, we want more flexibility and isolation, because if tomorrow the requirement for the list of browsers changes and you forget about the outdated version, then styles written according to the old standard will not go anywhere.

To solve these problems, I created a library «ngx-render-if-endpoints»

Complete list of features:

  • Dividing content into groups and configuring each group individually
  • Freedom of choice of names for endpoints at the stage of module configuration
  • Ability to set a group for a component and an array with endpoints at which it should be displayed
  • Ability to change an array with endpoints to display a component while the program is running
  • Get an object with the current endpoint for each group, get actual data when the endpoint changes in any of the groups
  • Ability to subscribe to hook events: removing and adding components to the DOM, initializing and destroying a directive
  • The ability to set the time after which the component will be inserted into the DOM from the moment the resize event ends
  • The ability to view the history of the current and previous endpoint
  • The library implements the "renderIfEndpoints" directive (it works the same way as ngIf, removes HTML from the DOM) and a service from which you can get the current configuration of the module in a usable format, as well as subscribe on changing of endpoint and receive actual endpoint information for each group.

Description of the "RenderIfEndpointsModule" module.

  • resizeDebounceTime: number - sets the time, after which the component will be inserted into the DOM from the moment the resize event ends
  • groupsAndEndpoints: Config<G, E> - sets groups and configuring each group individually

Methods:

forRoot

RenderIfEndpointsModule.forRoot<G, E>(
  {
    renderDebounceTime: number,
    groupsAndEndpoints: Config<G, E>
  }
) {...};

forChild

RenderIfEndpointsModule.forChild<G, E>(
  {
    renderDebounceTime: number,
    groupsAndEndpoints: Config<G, E>
  }
) {...};

or

RenderIfEndpointsModule.forChild<G, E>() {...};

Interface:

Config<G, E> = {
    readonly [key in G]: {
        [key: number]: E;
    };
};

Configuration example for forRoot():

app.module.ts

export type Groups = 'header' | 'body' | 'footer';
export type Endpoints = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';

@NgModule({
  imports: [
    BrowserModule,
    RenderIfEndpointsModule.forRoot<Groups, Endpoints>(
     {
        renderDebounceTime: 300,
        groupsAndEndpoints: {
          a: {
            400: 'extraSmall',
          },
          b: {
            320: 'extraSmall',
            576: 'small',
            768: 'medium',
            992: 'large',
            1230: 'extraLarge',
          },
          c: {
            450: 'extraSmall',
            740: 'small',
            968: 'medium',
            1100: 'large',
            1190: 'extraLarge',
          },
        }
      }
    ),
  ],
  declarations: [ AppComponent, TestComponent ],
  bootstrap:    [ AppComponent ],
  providers: [],
})
export class AppModule {}

Configuration example for forChild():

If you need to get settings from forRoot()

lazy.module.ts

@NgModule({
  declarations: [LazyComponent],
  imports: [
    CommonModule, 
    RouterModule.forChild(routes), 
    RenderIfEndpointsModule.forChild()
  ],
})
export class LazyModule {}

else

@NgModule({
  declarations: [LazyComponent],
  imports: [
    CommonModule, 
    RouterModule.forChild(routes), 
    RenderIfEndpointsModule.forChild(
      {
        renderDebounceTime: 1,
        groupsAndEndpoints: {
          a: {
            800: 'extraSmall',
          },
          b: {
            320: 'extraSmall',
            576: 'small',
            768: 'medium',
            992: 'large',
            1230: 'extraLarge',
          },
          c: {
            450: 'extraSmall',
            740: 'small',
            968: 'medium',
            1100: 'large',
            1190: 'extraLarge',
          },
        }
      }
    )
  ],
})
export class LazyModule {}

Description of the "renderIfEndpoints" directive.

Inputs:

  • rendeIfendpoint - takes an array of endpoints. If the current endpoint and endpoint from the list match, the component will be inserted into the DOM;
  • group - one of the groups defined in the configuration;
  • hideIfCurrentSizeLargerMax - define if the component will be displayed after the maximum endpoint.
[rendeIfendpoint]='[E]'
[group]='G'
[hideIfCurrentSizeLargerMax]='boolean'

Outputs:

  • initMonitoring – directive initialization event;
  • renderMonitoring – the event at which the directive define that the component will be added to the DOM (called from constructor, before initMonitoring);
  • hiddenMonitoring - the event at which the directive define that the component will not be added to the DOM or removed from the DOM (called from constructor, before initMonitoring);
  • destroyMonitoring – event on destruction of a directive (for example, by the ngIf directive).
(initMonitoring)='userFn($event)'
(renderMonitoring)='userFn($event)'
(hiddenMonitoring)='userFn($event)'
(destroyMonitoring)='userFn($event)'

From Outputs comes $event: EmittedValue<G, E> with state description and endpoints change history. Interfaces:

EmittedValue<G, E>: {
    group: G;
    resolveRender: boolean;
    renderStatus: RenderStatus;
    lastEndpoint: E;
    currentEndpoint: E;
    settings: {hideIfCurrentSizeLargerMax: boolean};
};
RenderStatus = 'wasDestroy' | 'nullEndpoint' | 'ok';

Template example:

app.component.html

<ng-template
    [renderIfEndpoints]="['extraSmall', 'small', 'medium', 'large', 'extraLarge']"
    [group]="'a'"
    (initMonitoring)="initMonitoring($event, 'ALL')"
    (renderMonitoring)="renderMonitoring($event, 'ALL')"
    (hiddenMonitoring)="wasHiddenMonitoring($event, 'ALL')"
    (destroyMonitoring)="wasDestroyMonitoring($event, 'ALL')"
    [hideIfCurrentSizeLargerMax]="true"
>
    <test-component 
        class="test-component test-component_all" 
        [value]="'Group - a. Endpoints - ALL'">
    </test-component>
</ng-template>

<ng-template
    [renderIfEndpoints]="['extraSmall']"
    [group]="'b'"
    (initMonitoring)="initMonitoring($event, 'extraSmall')"
    (renderMonitoring)="renderMonitoring($event, 'extraSmall')"
    (hiddenMonitoring)="wasHiddenMonitoring($event, 'extraSmall')"
    (destroyMonitoring)="wasDestroyMonitoring($event, 'extraSmall')"
    [hideIfCurrentSizeLargerMax]="false"
>
    <test-component 
        class="test-component test-component_extraSmall" 
        [value]="'Group - b. Endpoints - extraSmall'">
    </test-component>
</ng-template>

Description of the "RenderIfEndpointsInfoService" service

Methods:

get getSizeConf(): GroupsAndEndpointsConf<G, E>{...};
get groupsAndCurrentEndpoints(): Observable<GroupsSizeNow<G, E>>{...};

Interfaces:

GroupsAndEndpointsConf<G, E>: {
  G: {E: number}
};
GroupsSizeNow<G, E> = {
  G: E;
};

Component example:

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.scss' ],
})
export class AppComponent implements OnInit {

  public sizeConfig: GroupsAndEndpointsConf<Groups, Endpoints>;

  constructor(
    private resizeService: RenderIfEndpointsInfoService<Groups, Endpoints>,
  ) {}

  ngOnInit() {
    this.sizeConfig = this.resizeService.getSizeConf;
    this.resizeService.groupsAndCurrentEndpoints.pipe<GroupsSizeNow<Groups, Endpoints>>(
      takeUntil(this.unsubscribe$)
    ).subscribe((x) => {
      console.log(x);
    });
  }
  
  ngOnDestroy() {
    this.unsubscribe$.next(null);
    this.unsubscribe$.complete();
  }

  public renderMonitoring(value: EmittedValue<Groups, Endpoints>, id: string) {
    this.showLog(value, id, 'RenderMonitoring:');
  }
  public initMonitoring(value: EmittedValue<Groups, Endpoints>, id: string) {
    this.showLog(value, id, 'InitMonitoring:');
  }
  public wasHiddenMonitoring(value: EmittedValue<Groups, Endpoints>, id: string) {
    this.showLog(value, id, 'HiddenMonitoring:');
  }
  wasDestroyMonitoring(value: EmittedValue<Groups, Endpoints>, id: string) {
    this.showLog(value, id, 'DestroyMonitoring:');
  }

  showLog(value: EmittedValue<Groups, Endpoints>, id: string, monitoringGroup: string) {
    console.group(monitoringGroup);
    console.log('id: ' + id);
    console.log('group: ' + value.group);
    console.log('resolveRender: ' + value.resolveRender);
    console.log('renderStatus: ' + value.renderStatus);
    console.log('lastEndpoint: ' + value.lastEndpoint);
    console.log('currentEndpoint: ' + value.currentEndpoint);
    console.log('settings: ');
    console.log('- hideIfCurrentSizeLargerMax: ' + value.settings.hideIfCurrentSizeLargerMax);
    console.groupEnd();
  }
}