@adroit-group/ng-utils
TypeScript icon, indicating that this package has built-in type declarations

15.1.0 • Public • Published

All ContributorsspectatorMITcommitizenPRsstyled with prettierBuild Status

NG Utils

Handy utilities for Angular development

All notable changes to this project are documented in CHANGELOG.md file.

Versioning

Package version Angular version
0.3.x 13.x
14.x 14.x
15.x 15.x

Table of contents

Installation

Run npm install @adroit-group/ng-utils --save to install the library.

Usage

Include AdroitNgUtilsModule in your module

import { AdroitNgUtilsModule } from 'ng-utils';

@NgModule({
  imports: [
    AdroitNgUtilsModule.forRoot(),
  ]
})

Configuration

import { AdroitNgUtilsModule } from 'ng-utils';

@NgModule({
  imports: [
    AdroitNgUtilsModule.forRoot({

    }),
  ]
})

Back to top

Package structure

Ng utils is a monorepo that contains multiple packages. The main package that contains most of the useful stuff we've developed is @adroit-group/ng-utils. When you use the library, you should only import from this package. No need to specify any sub-package paths.

However there are some sub-packages that are useful on their own. These are:

  • @adroit-group/ng-utils/di-unchained - A DI library that allows you to use DI without the need to use the @Injectable decorator. This is useful when you want to use DI in a class that is not injectable (e.g. a mixin).

When you want to use these make sure to import from the proper sub-package.

Back to top

Decorators

Auto hooks

A decorator which ensures that the lifecycle hooks of components and directives that use mixins are called properly.

Quick rundown of usage:

  • Only one decorator per prototype chain.
  • All Angular lifecycle methods are bound automatically
  • You can bind custom methods by supplying their names in the first parameter array of the decorator
  • Don't call the super versions of the bound methods yourself
  • ngOnChanges doesn't work prior to Angular v10

Basic usage:

@Component({
  selector: 'my-comp',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss'],
})
@AutoHooks()
export class MyComp extends SubscriptionHandlerMixin() implements OnInit { ... }

Linking custom methods:

@Component({
  selector: 'my-comp',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss'],
})
@AutoHooks(['initialize'])
export class MyComp extends SubscriptionHandlerMixin() implements OnInit { ... }

Detailed introduction

The decorator applies automatic binding for the Angular specific lifecycle hooks.

This ensures that both the components' own methods and any other method on the mixins' prototypes are called in the same order in which constructor calls would do. (Starting with the last mixin class and calling continuosly toward the actual class that extends the mixins)

Nor the classes decorated with AutoHooks decorator neither the mixin or base classes applied to it are allowed to manually call the super version of the methods being bound.

That would result in unintended and multiple invocation of said methods.

The ngOnChanges hook is exempt from automatic invocation binding behavior when the decorator is used with an Angular version less than v10 as binding this lifecycle hooks results in a faulty behavior where the method does not receive the changes parameter object.

If a class's prototype chain contains real classes apart from the applied mixins those classes cannot use AutoHooks in tandem with the child class's decorator.

Back to top

RunIn Decorator

A decorator that restricts the usage of methods and accessors.

Methods and accessors decorated will only ran (when invoked from the client code), if the application's active platform matches those supplied to the decorator.

This makes it easy to optimize various execution scenarios and rid the application of execution paths that would cause problems in certain environments.

import {
  EApplicationPlatform,
  RunIn,
  RunInBrowser,
  RunInServer
} from '@adroit-group/ng-utils';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @RunInBrowser()
  public set setterThatUsesWindow(value: string) { ... }

  @RunInServer()
  public set setterThatSetsMetaTags(value) { ... }

  @RunIn(EApplicationPlatform.Browser)
  public methodThatUsesWindow(value: string) { ... }
}

RunInBrowser Decorator

An Alias decorator for @RunIn(EApplicationPlatform.Browser)

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @RunInBrowser()
  public set setterThatUsesWindow(value: string) { ... }
}

RunInServer Decorator

An Alias decorator for @RunIn(EApplicationPlatform.Server)

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @RunInServer()
  public set setterThatSetsMetaTags(value) { ... }
}

Back to top

Directives

NgLet Directive

A structural directive that allows the definition of template variables with ease.

<!-- Simple case -->
<div *ngLet="user.profileInfo.subscriptionInfo as userSubInfo">...</div>

<!-- With pipes -->
<div *ngLet="userData$ | async as userData">...</div>

<!-- Object notation syntax -->
<div
  *ngLet="{ 
    userData: userData$ | async, 
    cartPrice: cartData.totalPrice
  } as userAndCartData"
>
  <p>{{ userAndCartData.userData.name }}</p>
  <p>{{ userAndCartData.cartPrice | currency }}</p>
</div>

<!-- Array notation syntax -->
<div *ngLet="[userData$ | async, cartData.totalPrice] as userAndCartData">
  <p>{{ userAndCartData[0].name }}</p>
  <p>{{ userAndCartData[1] | currency }}</p>
</div>

The directive utilizes the Angular language service's capabilities and gives you full type checking in it's template.

Back to top

NgSubscribe Directive

A structural directive that allows the definition of template variables from async sources with ease. This directive does essentially the same as *ngLet="use$ | async as user" without having to use the async pipe explicitly.

<!-- Simple case -->
<div *ngSubscribe="subscriptionInfo.paymentDueAt$ as paymentDueAt">...</div>

<!-- Object notation syntax -->
<div
  *ngSubscribe="{ 
    userData: userData$, 
    paymentDueAt: subscriptionInfo.paymentDueAt$
  } as userAndSubscriptionData"
>
  <p>{{ userAndSubscriptionData.userData.name }}</p>
  <p>{{ userAndSubscriptionData.paymentDueAt | date }}</p>
</div>

<!-- Array notation syntax -->
<div
  *ngSubscribe="[
    userData$, 
    subscriptionInfo.paymentDueAt$
  ] as userAndSubscriptionData"
>
  <p>{{ userAndSubscriptionData[0].name }}</p>
  <p>{{ userAndSubscriptionData[1] | date }}</p>
</div>

The directive utilizes the Angular language service's capabilities and gives you full type checking in it's template.

Back to top

NgComponentOutlet Augmentation Directive

Augments the {@link NgComponentOutlet} directive to allow for binding of inputs and outputs.

In your lazy loaded component define the input and outputs you wish to bind to.

@Component({
  selector: 'lazy-loaded-component',
  template: `...`,
})
export class LazyLoadedComponent {
  @Input() public input1: string;

  @Output() public output1 = new EventEmitter<string>();
}

In the template where you wish to use the component, use the directive to bind the inputs and outputs.

<ng-container
  *ngComponentOutlet="lazyLoadedComp$ | async; inputs: { input1: 'test' }"
></ng-container>

If you want to bind to the lazy loaded components outputs' as well, then you have to use the non-micro-syntax version of the directive, as the micro-syntax version does not support binding to outputs.

<ng-container
  [ngComponentOutlet]="lazyLoadedComp$ | async"
  [ngComponentOutletInputs]="{ input1: 'test' }"
  [ngComponentOutletOutputs]="onComponentEvent($event)"
>
</ng-container>

Then in your component, you can handle the event like so:

export class MyComponentThatUsesLazyLoadedComponent {
  public onComponentEvent(event: NgComponentOutletEvent): void {
    if (event.key === 'output1') {
      // do something with the received event data through event.event
    }
  }
}

Back to top

NgFor Augmentation Directive

A utility directive that enhances Angular's *ngFor directive with extra functionality.

The directive is implicitly applied every time the original *ngFor is used and provides two additional template options for *ngFor.

You can supply both a no array and an empty array template to be rendered.

<p *ngFor="let item of items$ | async else loading empty blank">{{item}}</p>

credit: Alexander Inkin - https://medium.com/@waterplea

Medium article: https://medium.com/angularwave/non-binary-ngif-cfdf7c474852

Back to top

NgIf Augmentation Directive

A utility directive that enhances Angular's *ngIf directive with extra functionality.

The directive is implicitly applied every time the original *ngIf is used and provides two additional template options for *ngIf.

You can supply both a loading and error template for *ngIf. This can be particularly useful when you are working with async data

<p *ngIf="value$ | async as value else default or loading but error">
  {{value}}
</p>

credit: Alexander Inkin - https://medium.com/@waterplea Medium article: https://medium.com/angularwave/non-binary-ngif-cfdf7c474852

Back to top

NgRenderIn Directive

A structural directive that allows the rendering on templates based on the platform the application is running on.

The typical use case of the directive is to enable the usage of different templates for different rendering modes, eg.: usae a different view when the application is ran in SSR.

<app-splash-screen *appRenderIn="'browser'"></app-splash-screen>

There are two utility versions of this directive NgRenderInBrowser (*ngRenderInBrowser) and NgRenderInServer (*ngRenderServer) that do exactly what their names suggest.

NgRenderInBrowser Directive

A structural directive that only renders it's template if the application is ran in the browser.

<app-splash-screen *ngRenderInBrowser></app-splash-screen>

NgRenderInServer Directive

A structural directive that only renders it's template if the application is ran in the server environment (SSR).

<app-splash-screen *ngRenderInServer></app-splash-screen>

Back to top

FormControlName Augmentation Directive

A utility directive that exposes the FormControlName with exportAs so it can be stored in a template variable.

<input formControlName="testControl" #templateVariableName="formControlName" />
<span *ngIf="templateVariableName.invalid">Error message</span>

Back to top

Services

Platform Observer

An Injectable service that helps to identify the platform where the application runs.

Pipes

Method Invoker

A meta pipe that aims to replace most or all other pipes in your application. This pipe take a function and optionally it's arguments as parameter(s) an executes it. A context object can be supply to the pipe through Angular's DI system in turn enabling the usage and reference of the this parameter of the component or directive that defined the function.

Without context

<p>{{ getRelativeDateTime | invoke: orderDate }}</p>

With context

@component({
  ...
  providers: [
    {
      provide: METHOD_INVOKER_PIPE_HOST,
      useExisting: MyComponent
    }
  ]
})
export class MyComponent {
  ...

  public title = 'My App';

  public componentMethodThatUsesThis(): string {
    return this.title;
  }
}
<p>{{ componentMethodThatUsesThis | invoke }}</p>

Back to top

Safe Pipe

A pipe that bypasses Angular's built-in sanitization for the following security contexts:

  • HTML
  • Style
  • Script
  • URL
  • Resource URL

The pipe assumes HTML content to be sanitized by default.

Usage:

{{ value | safe }}
{{ value | safe:'html' }}
{{ value | safe:'style' }}
{{ value | safe:'script' }}
{{ value | safe:'url' }}
{{ value | safe:'resourceUrl' }}

Back to top

Mixins

Factory functions that return class definitions that can be used to dynamically constructor arbitrary prototype chains allowing you to use multiple inheritance with ease.

Subscription Handler

A Mixin function that provides functionality related to handling subscriptions. A class extending this mixin will have access to the onDestroy property that should be used in the various takeUntil operators throughout the streams defined by the concrete class. The mixin's subscription handling logic depends on it's ngOnDestroy method being called by Angular. If you have to define onDestroy$ lifecycle hook in your concrete class make sure to call super.ngOnDestroy as well or better yet use the AuthHooks decorator !

class MyComp extends SubscriptionHandlerMixin() {
  constructor() {
    super();
  }
}

Back to top

Media Observer

A Mixin function that provides functionality related to observing the browser viewport's size. A class extending this mixin will have access to various observables named after the convention followed by numerous CSS frameworks: xs$, sm$, md$, lg$, xl$, etc... These observable stream will always emit a boolean value indicating if the browser's viewport currently has a size fitting that breakpoint size criteria.

class MyComp extends MediaObserver() {
  constructor() {
    super();
  }
}
<div *ngIf="xs$ | async; else desktopTpl">...</div>

Back to top

TrackBy Handler

A Mixin function that provides functionality related to handling track-by functions for ngFor directives used in the component's template. A class extending this mixin will have access to a general purpose trackBy function that can be referenced in the component's template for ngFor directives' trackBy inputs. The trackBy function will try to use the received objects' ids as unique keys, however this behavior can be configured in the mixin's second parameter.

class MyComp extends TrackByHandlerMixin() {
  constructor() {
    super();
  }
}
<div *ngFor="pics of pictures; trackBy: trackBy">...</div>

Back to top

DI Unchained

A DI library that allows you to use DI without the need to use the @Injectable decorator. This is useful when you want to use DI in a class that is not injectable (e.g. a mixin).

There are TS decorators exposed by this package:

  • WithDiContext: A multi purpose decorator that allows you to use DI in any class without the need to use the @Injectable decorator.
  • DIContextProvider: A class decorator that marks an Angular NgModule as the DI provider for @Unchained classes.

WithDiContext

Decorator that frees your classes from the shackles of Angular DI. Allowing them to fully utilize it without requiring Angular's class decorators: Component, Directive, Injectable, Pipe, Module.

The decorator can be used as a class, method, get/set accessor decorator.

@Unchained()
class MyClass {
  router: Router;

  routerFn = () => inject(Router);

  static routerFn = () => inject(Router);

  constructor() {
    this.router = inject(Router);
  }

  getTitleService() {
    const title = inject(Title);

    console.log('title: ', title);
  }

  public get neta() {
    return inject(Meta);
  }

  public static get appRef() {
    return inject(ApplicationRef);
  }

  static getRouter() {
    return inject(Router);
  }
}

DIContextProvider

An Angular ngModule class decorator the marks an Angular NgModule as the DI provider for @Unchained classes.

You have to use this decorator on your root module (AppModule) in order to use @Unchained decorator on non Angular classes.

The normal DI resolution rules apply to Unchained classes and their providers as well. e.g.: Providers provided in their own lazy-loaded modules' providers array ARE NOT resolvable in other modules contexts.

@DI()
@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

Back to top

Development environment

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Run npm run packagr to build the library. The build artifacts will be stored in the dist directory.

/@adroit-group/ng-utils/

    Package Sidebar

    Install

    npm i @adroit-group/ng-utils

    Weekly Downloads

    15

    Version

    15.1.0

    License

    MIT

    Unpacked Size

    758 kB

    Total Files

    229

    Last publish

    Collaborators

    • jonatanferenczfi