Giter VIP home page Giter VIP logo

ng-helpers's Introduction

A library that provides helpers for your Angular projects that speed up the development or solve tricky problems.

Ng Helpers logo

Build Status Version License

ng Helpers

Table of contents:

Installation

npm install --save ng-helpers

Package

This package consists of the following helpers:

Fragment component

The Fragment component provides the solution for cases when you don't want your component template to be wrapped in a named tag. Such cases include:

  • having multiple root nodes
  • having a native element as a root node

An angular component gets wrapped in a single named tag, provided by the selector property. Each component must therefore have a single root DOM note wrapping an entire body. There are, however, situations when we would like to have multiple root nodes, for example, if our component is to render li elements in the ul:

With parent template defined as:

<ul>
  <my-list/>
</ul>

And component defined as:

@Component({
  selector: 'my-list',
  template: `
    <li>Apple</li>
    <li>Banana</li>
    <li>Orange</li>
  `
})
export class MyList {}

renders as

<ul>
  <my-list>
    <li>Apple</li>
    <li>Banana</li>
    <li>Orange</li>
  </my-list>
</ul>

which is, of course, invalid HTML and might additionally break our styles. What we would like is for it to render as:

<ul>
  <li>Apple</li>
  <li>Banana</li>
  <li>Orange</li>
</ul>

Usage

The Fragment component replaces the root element with the contents of the template defined as the first child element.

@Component({
  selector: 'my-list',
  template: `
    <ng-template>
      <li>Apple</li>
      <li>Banana</li>
      <li>Orange</li>
    </ng-template>
  `
})
export class MyList extends FragmentComponent implements OnInit {
  constructor(vcRef: ViewContainerRef) {
    super(vcRef);
  }

  ngOnInit(): void {
    this.appendDOM();
  }
}

and given parent template:

<ul>
  <my-list/>
</ul>

will render as:

<ul>
  <li>Apple</li>
  <li>Banana</li>
  <li>Orange</li>
</ul>

Possible usages are rendering partial li lists, table rows or columns or any other parts of DOM that require the specific parent DOM element.

Let directive

The angular template is often using deep nested object values or observables in multiple places. The common pattern is to use the ngIf structural directive to extract the value:

<div *ngIf="loadingState$ | async as loadingState">
  <my-table 
    [loading]="loadingState" 
    [rows]="rows"
    [columns]="columns">
  </my-table>
  <button 
    (click)="addRow()"
    [disabled]="loadingState === LoadingState.Loading"
  >Add row</button>
</div>

The non-so-obvious flaw of the above approach is that whenever the loadingState has a falsy value (0, null, false, etc.) the entire block will not be rendered. The ngLet solves this by always rendering the inner content and passing the value no matter if falsy or truthy.

Usage

Before using the directive we need to include the module.

import { LetModule } from 'ng-helpers';

@NgModule({
  imports: [
    LetModule
  ]
})
export class AppModule { }

Now we can use it in our component templates

<div *ngLet="loadingState$ | async as loadingState">
  The {{ loadingState }} will be available here
</div>

We can also use it to put deep nested values in variables for better readability:

<form [formGroup]="myForm">
  <input formControlName="name" required />
  <ng-container *ngLet="myForm.controls.name as name">
    <div *ngIf="name.invalid && (name.dirty || name.touched)"
        class="alert alert-danger">

      <div *ngIf="name.errors.required">
        Name is required.
      </div>
      <div *ngIf="name.errors.minlength">
        Name must be at least 4 characters long.
      </div>
      <div *ngIf="name.errors.forbiddenName">
        Name cannot be Bob.
      </div>
    </div>
  </ng-container>
</form>

Media

The Media package consists several utilities used to responsive content handling in Angular. Those utilities include:

You can read more about the motivation in my blog post Responsive Angular Components. It's an upgrade from CSS based responsive design by media queries as it allows us to manipulate the actual content of the DOM instead of just it's visibility. By having too many differences between responsive states we are polluting the DOM by keeping there elements that are not visible.

This can be avoided in Angular by using services, directives or components that will not be rendered unless the certain media query is matched. For example we could use the service to fetch data from different endpoints depending on media or use a component to optionally render a certain DOM block.

Usage

In order to use any of the utility parts of the media package we need to include the MediaModule:

import { MediaModule } from 'ng-helpers';

@NgModule({
  imports: [
    MediaModule
  ]
})
export class AppModule { }

Media service

The Media service is a service that exposes an Observable returning matched state of the given media queries.

Usage:

@Component({
  selector: 'foo-bar',
  template: `
    <div *ngIf="mediaService.match$ | async">
      I am shown only in the portrait mode
    </div>
  `
})
class FooBarComponent { 
  private sub: Subscription<boolean>;

  constructor(private readonly mediaService: MediaService) {};

  ngOnInit() {
    // initialize listener
    this.mediaService.setQuery('(orientation: portrait)');
    // subscribe to changes programatically or via template
    this.mediaService.match$
      .pipe(
        distinctUntilChanged(),
        map(isPortrait => isPortrait ? 'Potrait' : 'Landscape')
      )
      .subscribe(orientation => console.log(`Orientation changed! New: ${orientation}`));
  }
}

In order to avoid memory leaks don't forget to unsubscribe all the listeners! The observable will be automatically closed when service is destroyed.

Media component

The Media component is an angular wrapper component for manipulating the rendering of the content based on the matched media queries.

Usage:

@Component({
  selector: 'foo-bar',
  template: `
    <use-media query="(min-width: 768px)">
      I am visible only on desktop
    </use-media>
    <use-media query="(orientation: portrait)">
      I am visible only on portrait mode
    </use-media>
  `
})
class FooBarComponent { }

Media directive

The Media directive is a structural directive for manipulating the content of the template based on the matched media queries.

Usage:

<div *media="'(min-width: 768px)'">I will be shown only on desktop</div>
<my-portait-component *media="'(orientation: portrait)'"></my-portait-component>

Common media queries

Some of the most common media queries:

Query Result
(max-width: 768px) Used for mobile views
(min-width: 769px) and (max-width: 1200px) Standard browsers size
(min-width: 1201px) and (max-width: 1400px) Wide browsers
(min-width: 1401px) Ultra-wide browser
(orientation: landscape) Landscape mode on handheld device or if width > height
(orientation: portrait) Portrait mode on handheld device or height > width

You can find more on CSS Tricks post.

License

MIT

Copyright (c) 2019-present, Miroslav Jonas

ng-helpers's People

Contributors

meeroslav avatar dependabot[bot] avatar

Stargazers

Sajeev avatar Aymeric avatar Ferdi Schmidt avatar Rodolfo Beccari de Oliveira avatar Sang Nguyen avatar Javier Pérez avatar Vadym Parakonnyi avatar Nathan Walker avatar Pawel Lbs avatar Tomas Rimkus avatar John Bauer avatar Laurent CESSAT avatar Jonas avatar Petter avatar muhammad samir avatar André Wehlert avatar  avatar

Watchers

 avatar  avatar

ng-helpers's Issues

NgLetContext / NgLetDirective not supporting types

Please consider following example:

export class Parent {
    construct(public child: Child)
}

export class Child {
    construct(public firstName: string, public lastName: string)
}

in HTML 
<div *ngLet="parent.child as child">
{{child.firstName}}
</div>

{{child.firstName}} causes a warning in HTML template, because type of child is any.

Reason:

class NgLetContext {
  $implicit: any = null;
  ngLet: any = null;
}

Please add types to NgLetContext & NgLetContext that correct type is also propaged in HTML templates.

Possible solution:

class NgLetContext<T> {
  $implicit: T = null;
  ngLet: T = null;
}

export class NgLetDirective<T> implements OnInit {
  private readonly context = new NgLetContext<T>();
  
  @Input()
  set ngLet(value: Tny) {
    this.context.$implicit = this.context.ngLet = value;
  }
  
...

Angular 12 NG0303: Can't bind to 'ngLet' since it isn't a known property of 'ng-container'

NG0303: Can't bind to 'ngLet' since it isn't a known property of 'ng-container'

<ng-container *ngLet="currentRoute$ | async as currentRoute">
...
</ng-container>

LetModule imported in module:

...
import {LetModule} from 'ng-helpers';
....

@NgModule({
    imports: [
        CommonModule,
        RouterModule,
        LetModule,
...
    ],
...

node: v12.22.1
npm: 6.14.12
angular: 12.0.4

Just for test, i copied code "https://github.com/meeroslav/ng-helpers/blob/master/projects/ng-helpers/src/lib/let/let.directive.ts" into our Angular 12 project, i worked.

"*media" directive works fine, could be problem with "ng" prefix ?

type casting necessary due to insufficient types on ngrx

Scope: ngrx helpers

ActionCreator extends the inner Creator type by adding TypedAction, however actual return type should be typed Creator - the function that returns original response hydrated with type.

This forces us to cast ActionCreator calls to Actions (and Observable)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.