Function APIs for Angular to create component, directive and service using function composition api in declarative way, inspired by Vue Function APIs and React Hooks.
- Why Function APIs?
- Installation
- Usage
- Lifecycle
- ref
- computed
- observe
- watch
- inject
- createProvider
- Helpers
- License
- FAQs
- More declarative way to write Angular Components, Directives and Services
- Reuse same functions between different components/directives so no need to create services to splitting out methods to reduce component file or making it reusable.
- Easy to split up functionality into functions of large component without creating unnecessarily more components.
- Encourages to write implementation detail free tests
This library is distributed via npm registry:
npm install --save ngx-hooks
This library has @angular/core
and rxjs
as peerDependencies
.
This package contains uncompiled typescript (.ts) files, you need to configure it manually.
To enable typescript compilation of this package along with your angular project, you need to configure source files in tsconfig.json
like:
{
// ...
include: ['./node_modules/ngx-hooks/**/*.ts'],
// ...
}
Simple usage of function component
import { Component } from '@angular/core';
import { FunctionComponent, NgHooksContext, ref } from 'ngx-hooks';
@Component({
selector: 'app-component',
template: '<h1>{{title}}</h1>',
})
@FunctionComponent() // <- to make Function Component
class AppComponent {
title: string;
// required by @FunctionComponent() and must be static
static ngHooks(context: NgHooksContext<AppComponent>) {
const title = ref('Angular Function APIs');
return { title };
}
}
Simple usage of a function directive which highlights a text with yellow color
import { Directive, ElementRef } from '@angular/core';
import { FunctionDirective, NgHooksContext, inject } from 'ngx-hooks';
@Directive({
selector: '[highlight]',
})
@FunctionDirective() // <- to make Function Directive
class HighlightDirective {
// required by @FunctionDirective() and must be static
static ngHooks(context: NgHooksContext<HighlightDirective>) {
const elementRef = inject(ElementRef);
elementRef.nativeElement.style.backgroundColor = 'yellow';
}
}
Lifecycle can be used in Function component/directives and Provider or any composed function. It must be used in ngHooks
function body or custom compose function body, can't be used in nested callback
import { onDestroy, ref } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const count = ref(0);
const interval = setInterval(() => {
ref.value++;
}, 1000);
onDestroy(() => {
clearInterval(interval);
});
return { count };
}
}
onInit
onDestroy
onChanges
onDoCheck
onAfterContentInit
onAfterContentChecked
onAfterViewInit
onAfterViewChecked
Note: Custom lifecycle can be created via composeLifecycle
here
ref()
is to creating a reactive state
import { ref } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const title = ref('Angular');
setTimeout(() => {
title.value = 'Angular Function API';
}, 3000);
return { title };
}
}
computed
takes a getter function in first argument and returns a computed reactive value based on a getter function.
import { computed, ref } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const count = ref(0);
const double = computed(() => count.value * 2);
setInterval(() => {
count.value++;
}, 1000);
return { double };
}
}
it does also take second argument as setter which will be invoked when computed reactive value is set.
import { computed, ref } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const count = ref(0);
const double = computed(() => count.value * 2, (double) => (count.value = double / 2));
double.value = 1000;
return { double };
}
}
Note: assigning to a computed value will not have any effect if setter is not provided
observe
returns a reactive value by observing a property of any object.
import { observe, computed } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const id = observe(context, (context) => context.id);
const currentUser = computed(() => USERS.find((user) => user.id === id.value));
return { currentUser };
}
}
It runs a watcher function when ever dependency or dependencies change. Dependency can be a reactive value or a getter function.
import { watch, observe, ref } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const id = observe(context, (context) => context.id);
const user = ref(null);
watch(id, async (newId, oldId, onCleanup) => {
const result = await fetch('some.domain.url/api/user/' + newId);
const data = await result.json();
user.value = data;
onCleanup(() => {
// cleanup functionality
});
});
return { user };
}
}
import { watch, observe } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const id = observe(context, (context) => context.id);
const params = observe(context, (context) => context.params);
watch([id, () => params.value.user_role], ([newId, newUserRole], [oldId, oldUserRole], onCleanup) => {
// ...
onCleanup(() => {
// cleanup functionality
});
});
// ...
}
}
watch
takes options
object (optional) in last function parameter.
watch(
id,
() => {
// call immediately when id changes and will not call immediately
},
{ mode: 'sync', lazy: true }
);
-
Watch mode:
By default, it flushes after content checked. this behaviour can be changed via
mode
option.sync
: flushes synchronouslycontent
: flushes after content checked (default)view
: flushes after view checked
-
Lazy Watch:
When lazy is
true
, watcher function will not run immediately. (default isfalse
)
Note: watch
fallbacks to sync
mode if it is used in createProvider
.
inject
is same as Angular's Injector.get
.
import { inject } from 'ngx-hooks';
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const store = inject(Store);
// do anything with store
}
}
createProvider
creates provider which can be injected in components, directives or providers. the return value must be an object.
import { createProvider, inject } from 'ngx-hooks';
const [LogService, LogServiceProvider] = createProvider('LogService', () => {
function log(...args) {
console.log('Log:', ...args);
}
return { log };
});
@Component({
// ...
providers: [LogServiceProvider],
})
@FunctionComponent()
class AppComponent {
static ngHooks(context: NgHooksContext<AppComponent>) {
const logService = inject(LogService);
logService.log('log some useful stuff');
}
}
-
Usage with object destructuring:
import { createProvider } from 'ngx-hooks'; const { token: LogService, provider: LogServiceProvider } = createProvider('LogService', () => { // ... });
-
Usage with
providedIn
:providedIn
isroot
by defaultimport { createProvider } from 'ngx-hooks'; const { token: LogService, provider: LogServiceProvider } = createProvider('LogService', { providedIn: AppModule, factory: () => { // ... }, });
Note: In provider, only onDestroy
lifecycle can be used other lifecycle will have no effects.
-
it can be use to compose custom lifecycle which give can be used in composed functions as well.
import { composeLifecycle } from 'ngx-hooks'; const onWriteValue = composeLifecycle<(value: string) => void>('writeValue'); @Component({ // ... }) @FunctionComponent({ lifecycle: [onWriteValue], }) class AppComponent { static ngHooks(context: NgHooksContext<AppComponent>) { onWriteValue((value) => { // do something with value }); } }
-
it can be used to un-wrap ref or Object/Array of refs deeply
import { fromRef, ref } from 'ngx-hooks'; const id = ref('001'); fromRef(id); // '001' const refsArray = [ref(1), ref(2)]; fromRef(refsArray); // [1, 2]; const refMap = { ref1: ref('value1'), deep: { nestedRef: ref('value2'), }, }; fromRef(refMap); // {ref1: 'value1', deep: {nestedRef: 'value2'}}
-
Why ngHooks method is static method?
To avoid confusion of
this
keyword as we restricted the usage of it.As we don't allow to use
this
inngHooks
function but if it would be a class method thenthis
should be referenced toclass
instance as per semantic of Javascript.