Coder Social home page Coder Social logo

ng-turkey / ngx-context Goto Github PK

View Code? Open in Web Editor NEW
134.0 25.0 9.0 2.01 MB

Angular Context: Easy property binding for router outlet and nested component trees.

License: MIT License

JavaScript 5.09% TypeScript 93.80% HTML 0.52% CSS 0.59%
angular context data-binding property-binding utility provider consumer router-outlet

ngx-context's Introduction

Angular Context (ngx-context)

Angular Context is a library to bind data to deeply nested child components without passing properties through other components or getting blocked by a router outlet.

If you would like to have further information on why you need a library like this, you may find the reasons below. Otherwise, skip to the quickstart or usage section.

Check demo application out for a preview.

You may also find the introductory blog post helpful.

Reasons to Use This Library

Data-binding and input properties are great. However, working with them has some challenges:

  • Passing properties through several layers of the component tree is known as prop-drilling and it is time consuming and error prone to maintain.
  • The intermediary components become bloated with properties and methods just to pass data from parent to child and vice versa.
  • When a component is loaded via router-outlet, data-binding is not available and prop-drilling is no longer an option.

This library is designed to improve developer experience by fixing all issues above. It provides context through dependency injection system behind-the-scenes and lets your deeply nested dumb components consume this context easily. It is inspired by React Context, but differs in implementation and is 100% tailored for Angular.

Quickstart

Installation

Run the following code in your terminal:

yarn add ngx-context

or if you are using npm:

npm install ngx-context

Setup Before Initial Use

Import NgxContextModule into your root module like:

import { NgxContextModule } from 'ngx-context';

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

How to Provide Context from Parent Component

Simply put a ContextProviderComponent around the children and refer to property names to provide.

@Component({
  selector: 'parent-component',
  template: `
  <context-provider provide="someProp someOtherProp">

    <!--
    components consuming the context can be deeply nested
    within the components placed here (inside context provider)
    and be able to consume someProp and someOtherProp
    -->

  </context-provider>
  `,
})
export class ParentComponent {
  someProp: string = 'Test';

  someOtherProp: boolean = true;

  notProvided: number = 5;

  alsoNotProvided: Observable<> = empty();
}

How to Consume Context in Child Component

You may consume any provided context in a component by placing a ContextConsumerDirective on it. This component can either be a direct or a deeply nested child of the context provider.

<!-- child component will be able to consume all provided props -->

<child-component contextConsumer></child-component>

Usage

ContextProviderComponent

The name of the props to be provided is set by provide input and it can take string or Array<string> values.

<context-provider provide="someProp someOtherProp">
  <!-- consumers will consume someProp and someOtherProp -->
</context-provider>

— or —

<context-provider [provide]="['someProp', 'someOtherProp']">
  <!-- consumers will consume someProp and someOtherProp -->
</context-provider>

Provided property names can be dynamically set.

<context-provider [provide]="propertiesToProvide">
  <!-- consumers will consume properties defined by propertiesToProvide -->
</context-provider>

Provided property names can be mapped.

<context-provider
  provide="someProp"
  [contextMap]="{someProp: 'someOtherPropName'}"
>
  <!-- consumers will consume someOtherPropName -->
</context-provider>

Context consumers or their parents should be wrapped by ContextProviderComponent in the parent component.

<context-provider provide="someProp someOtherProp">
  <some-context-consumer></some-context-consumer>
  <some-other-context-consumer></some-other-context-consumer>
  <parent-of-context-consumer></parent-of-context-consumer>
  <grand-grand-grand-parent-of-context-consumer></grand-grand-grand-parent-of-context-consumer>
</context-provider>

More than one ContextProviderComponent can be placed in a parent component.

<context-provider provide="someProp someOtherProp">
  <some-context-consumer></some-context-consumer>
  <parent-of-context-consumer></parent-of-context-consumer>
</context-provider>

<context-provider provide="yetAnotherProp">
  <some-other-context-consumer></some-other-context-consumer>
</context-provider>

Router outlets have no effect on context and can be safely used.

<context-provider provide="someProp someOtherProp">
  <router-outlet></router-outlet>
</context-provider>

ContextConsumerComponent

The easiest way to consume a context is to place a ContextConsumerComponent inside a child component. It will be able to consume context once provided and behave normally when not.

<!--
place this inside the template of the consumer component
consumer will consume any property provided
-->

<context-consumer></context-consumer>

The name of specific props to be consumed can be set by consume input and it can take string or Array<string> values.

<!-- consumer will consume someProp and someOtherProp -->

<context-consumer consume="someProp someOtherProp"></context-consumer>

— or —

<!-- consumer will consume someProp and someOtherProp -->

<context-consumer [consume]="['someProp', 'someOtherProp']"></context-consumer>

Consumed property names can be dynamically set.

<!-- consumer will consume properties defined by propertiesToConsume -->

<context-consumer [consume]="propertiesToConsume"></context-consumer>

Consumed property names can be mapped.

<!-- consumer will consume someOtherPropName -->

<context-consumer [contextMap]="{someProp: 'someOtherPropName'}"></context-consumer>

ContextConsumerDirective

If a component cannot take ContextConsumerComponent in (e.g. 3rd-party components), you can use ContextConsumerDirective on them.

<!-- consumer will consume any property provided -->

<child-component contextConsumer></child-component>

The name of specific props to be consumed can be set by contextConsumer input and it can take string or Array<string> values.

<!-- consumer will consume someProp and someOtherProp -->

<child-component contextConsumer="someProp someOtherProp"></child-component>

— or —

<!-- consumer will consume someProp and someOtherProp -->

<child-component [contextConsumer]="['someProp', 'someOtherProp']"></child-component>

Consumed property names can be dynamically set.

<!-- consumer will consume properties defined by propertiesToConsume -->

<child-component [contextConsumer]="propertiesToConsume"></child-component>

Consumed property names can be mapped.

<!-- consumer will consume someOtherPropName -->

<child-component [contextMap]="{someProp: 'someOtherPropName'}"></child-component>

ContextDisposerDirective

There are some cases where you will need the context on a higher level and end up putting properties on a middle component's class. For example, in order to make reactive forms work, a ContextConsumerComponent will most likely be used and the consumed properties will have to be added to the wrapper component. This is usually not the preferred result. After all, we are trying to keep intermediary components as clean as possible. In such a case, you can use ContextDisposerDirective on an <ng-template> and make use of template input variables.

<!-- disposer will dispose any property provided under context -->

<ng-template contextDisposer let-context>
  <child-component [someProp]="context.someProp"></child-component>
</ng-template>

The name of specific props to be disposed can be set by contextDisposer input and it can take string or Array<string> values.

<!-- disposer will dispose someProp and someOtherProp under context -->

<ng-template contextDisposer="someProp someOtherProp" let-context>
  <child-component
    [prop1]="context.someProp"
    [prop2]="context.someOtherProp"
  ></child-component>
</ng-template>

— or —

<!-- disposer will dispose someProp and someOtherProp under context -->

<ng-template contextDisposer="['someProp', 'someOtherProp']" let-context>
  <child-component
    [prop1]="context.someProp"
    [prop2]="context.someOtherProp"
  ></child-component>
</ng-template>

Properties to dispose can be dynamically set.

<!-- disposer will dispose properties defined by propertiesToDispose under context -->

<ng-template [contextDisposer]="propertiesToDispose" let-context>
  <child-component
    [prop1]="context.someProp"
    [prop2]="context.someOtherProp"
  ></child-component>
</ng-template>

Disposed property names can be individually assigned to template input variables.

<!-- disposer will dispose prop1 and prop2 -->

<ng-template
  contextDisposer
  let-prop1="someProp"
  let-prop2="someOtherProp"
>
  <child-component [prop1]="prop1" [prop2]="prop2"></child-component>
</ng-template>

Note: If you are wondering how you can implement reactive forms using Angular Context, please refer to the demo application.

Caveats / Trade-offs

There are several issues which are simply not addressed yet or impossible with currently available tools.

  • There can be only one provider for any component sub-tree, altough using a provider in combination with a consumer would help transfer parent provider down the tree.
  • Several consumers can consume the same provider, but a consumer can only have one provider (first provider up the tree).
  • There is a performance penalty to be paid due to use of getters and setters. Although this penalty is kept as small as possible, it is not benchmarked yet.
  • Debugging may become more difficult for child components, because their behavior will be defined by the context magically provided by some parent. ¯\_(ツ)_/¯

Roadmap

  • Component to provide context

  • Component and directive to consume context

  • Directive to dispose context

  • Test coverage

  • Documentation & examples

  • Permissive license

  • Inclusive code of conduct

  • Issue submission templates

  • Contribution guidelines

  • CI integrations

  • Benchmarks

  • Optimization

ngx-context's People

Contributors

armanozak avatar bnymncoskuner avatar denizguzel avatar dependabot[bot] avatar mehmet-erim avatar mehmetakifalp avatar muhammedaltug avatar semyonic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngx-context's Issues

Any plans for adding support for Angular 16 ?

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. adding library in application using Angular 16 results in following error

context.module.d.ts(1, 22):  This likely means that the library which declares NgxContextModule is not compatible with Angular Ivy. 
Check if a newer version of the library is available, and update if so.
Also consider checking with the library's authors to see if the library is expected to be compatible with Ivy.

Expected behavior
Library should work with Angular 16

Desktop (please complete the following information):

  • All

Smartphone (please complete the following information):

  • All

Additional context
Add any other context about the problem here.

ngx-context@2 errors with @angular/[email protected]

After installing ngx-context@2 and making NgContextModule an import in my AppModule, I get the following error and the application fails to start:

core.js:1119 Uncaught Error: Type NgxContextModule does not have 'ɵmod' property.
    at getNgModuleDef (core.js:1119)
    at recurse (core.js:25273)
    at recurse (core.js:25284)
    at registerNgModuleType (core.js:25269)
    at new NgModuleFactory$1 (core.js:25383)
    at compileNgModuleFactory__POST_R3__ (core.js:29082)
    at PlatformRef.bootstrapModule (core.js:29332)
    at Module.6790 (main.ts:47)
    at __webpack_require__ (bootstrap:19)
    at __webpack_exec__ ((index):23)

After removing NgxContextModule from my imports, the application runs normally.

I am using "@angular/cli": "^12.2.11" with the following environment:

image

Upgranding to angular 9/Ivy compiler

Is your feature request related to a problem? Please describe.
Yes. The current version does NOT supports Ivy/angular 9

Describe the solution you'd like
Support for angular 9
Angular 9 release is pending. I am using the current release candidate, but your package does not support angular 9/Ivy.

Please consider upgrading to angular9/Ivy

Cheers

Slice context

Is your feature request related to a problem? Please describe.
I can't two way bind specific properties in objects that I assign to the context.

Describe the solution you'd like
Assuming I have this child template:

  <ng-template contextDisposer="message" let-context>
        <input [ngModel]="context.message" />
  </ng-template>

I'd like to be able to do this:

  <ng-template contextDisposer="message" let-context>
        <input [ngModel]="context.message.title" ... />
  </ng-template>

The reason being then only a singular object can be a context for similar but different components, engaging date pickers, sliders, and/or image uploads into a singular model. The components can manipulate their own slice of the object which is then shipped to the server.

Thanks for this awesome framework!!

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.