Skip to content
This repository has been archived by the owner on May 31, 2021. It is now read-only.

Latest commit

 

History

History
748 lines (522 loc) · 18.7 KB

CHANGELOG.adoc

File metadata and controls

748 lines (522 loc) · 18.7 KB

Changelog

9.1.1

Features

  • make computed lazy

  • check refs during afterContentChecked and afterViewChecked

Bug Fixes

  • fix onChanges hook

  • trigger change detection when effect is invalidated

9.1.0

I have decided to release this version as it is so that future work can be directed towards the upcoming release of Angular 10. The upstream library this is based on (@vue/reactivity) is still in BETA and a number of changes have been made to make it compatible with Angular, some of which depend on undocumented private APIs. If you spot a mistake or get stuck, please open an issue.

This minor release introduces a Composition API similar to @vue/reactivity, with special considerations for Angular and RxJS interop in particular. Key features like effect invalidation, lifecycle hooks and reactivity have been replicated in terms of the API, with some minor changes to make it fit with how Angular’s component lifecycle and change detection process works.

This is still a work in progress!

Note
The 9.0.x decorator API has been deprecated and will be removed in 10.0.0.

Example illustrating some core APIs:

import { computed, defineComponent, defineInjectable, inject, observe, ref } from "ng-effects"

@Injectable({ providedIn: "root" })
export class ApiService extends defineInjectable(() => {
    const http = inject(HttpClient)

    function loadData(id: number) {
        return http.get<{ name: string }[]>(`http://www.example.com/api/entity/${id}`)
    }

    return {
        loadData
    }
}) {}

@Component({
    selector: "app-root",
    inputs: ["count"]
})
export class AppRoot extends defineComponent(() => {
    const count = ref(0)
    const countChange = new EventEmitter<number>()
    const double = computed(() => count.value * 2)
    const { loadData } = inject(ApiService)

    const onLoadData = effect<number>().pipe(
        exhaustMap(loadData)
    )

    function increment() {
        count.value += 1
        onLoadData.next(count.value)
    }

    watchEffect((onInvalidate) => {
        console.log(count.value)
        // effect invalidation callback
        onInvalidate(() => countChange.next(count.value))
    })

    observe(onLoadData, (list) => {
        for (item of list) {
            console.log(item.name)
        }
    })

    return {
        count,
        double,
        increment
    }
}) {}

Features

  • use same reactive and readonly implementation as @vue/reactivity

  • add effect factory

  • add observe hook

  • add observeError hook

  • add fromRef util

Bug fixes

  • fix some incorrect types when unwrapping refs

  • don’t unwrap refs returned from services

  • ensure effects run synchronously when used outside of a view context

Misc

  • copy @vue/reactivity specs for reactive, ref and computed

9.1.0-rc.7

This release features a core rewrite that reduces the code footprint, fixes bugs and aligns more closely with the behaviour of Vue Composition API. Component classes now extend a base class produced by defineComponent, which takes a factory function analagous to the setup function from Vue. Directives and services receive similar treatment with defineDirective and defineInjectable. Refs are introduced, bringing the API coverage close to parity.

Features

  • add ref, shallowRef and customRef

  • remove effect and EffectFactory

  • rename Computed to computed, remove $ alias, rework with refs

  • add readonly and shallowReadonly

  • add toRef, toRefs, isRef, unref utils

  • remove connectable and Connectable

  • add defineComponent, defineDirective and defineInjectable

  • rework inject using ɵsetCurrentInjector and ɵɵdirectiveInject

  • rename and add missing lifecycle methods

Misc

  • reduce bundle size

  • add more tests

Bug fixes

  • ensure correct injector is used when injecting values in components and services

9.1.0-rc.6

Features

  • allow async/await in effects

  • include computed values in tracked dependencies

Misc

9.1.0-rc.5

This releases reintroduces Effect as a reactive type and adds support for computed properties with Computed (alias: $). It also adds options to control when watchEffect is flushed.

Effect works much the same as HostEmitter in 9.0.0 except it now takes an optional OperatorFunction argument. This makes it simple to delay or transform value emissions from the source. Users can bypass this operator by subscribing to new Effect(fn).source.

Computed properties are implemented as a function setter/getter. Calling the method without arguments will return the memoized value or recompute the value if reactive dependencies have changed. Optional arguments can also be passed in as part of the value computation. These arguments are also memoized. Only the most recent deps/arguments are cached.

@Component({
    template: `
        <button (click)="increment(1)">Click</button>
        <div>Offset: {{ offset(1) }}</div>
    `
})
export class NgComponent extends Connectable {
    @Input()
    count = 0

    @Output()
    countChange = new Effect<number>(delay(500))

    increment = new Effect<number>()

    offset = $((offset: number) => this.count + offset)

    ngOnConnect() {
        const { increment, countChange } = this

        effect((onInvalidate) => {
            const cleanup = increment.subscribe((num) => {
                this.count += num
                countChange(this.count)
            })
            onInvalidate(cleanup)
        })
    }
}

You can now configure watchEffect to be flushed before the component view updates. The current timings are:

Option Lifecycle

sync

Whenever a reactive property changes.

pre

After all afterContentChecked hooks have run.

post (default)

After all afterViewChecked hooks have run.

Features

  • support pre and sync options for watchEffect

  • add new Effect and Computed types

  • rework onInvalidate as an injected argument to effects

  • remove global onInvalidate hook

  • reactive no longer wraps methods in an execution context

Misc

  • add more tests

Deprecations

deprecate HostEmitter

9.1.0-rc.4

Performance improvements

  • flush watchEffect after component view is checked

  • remove ngDoCheck hook in Connectable

  • remove redundant diff checks

Bug fixes

  • track deps for uninitialised fields

9.1.0-rc.3

Bug fixes

  • fix unintended calls to onChanges

  • reuse proxy ref in connectable providers

  • fix isProxy util

9.1.0-rc.2

This release adds reactive hooks for Angular component lifecycle methods:

@Component()
export class MyComponent extends Connectable {
    ngOnConnect() {
        // called during ngOnInit

        onChanges((changes) => {
            // when inputs change
        })
        afterContentInit(() => {
            // content children initialised
        })
        afterViewInit(() => {
            // after first render
            effect(() => {
                // starts after component mounted
            })
        })
        afterContentChecked(() => {
            // after content children updated
        })
        afterViewChecked(() => {
            // after each render
        })
        onDestroy(() => {
            // when component destroyed
        })
    }
}

Features

  • add more lifecycle hooks

  • rework onChanges hook so it only fires when inputs are changed

  • return stop handler from effects

Bug fixes

  • fix invalidations for effects inside lifecycle hooks

  • export onInvalidate hook

  • ensure invalidations are only called once on destroy

Misc

  • rename whenRendered to afterViewChecked

9.1.0-rc.1

This release adds side effect invalidation hooks. These hooks can be called inside the top level of an effect or connected component method to register side effect invalidations, such as cancelling a http call. There are two global hooks available: onInvalidate and onDestroy.

OnInvalidate is called each time an effect or connected component method is invoked, as well as when the component is destroyed.

OnDestroy is only called when the component is destroyed.

@Component()
export class MyComponent extends Connectable {
    private http = inject(HttpClient)
    count = 0

    asyncMethod() {
        const sub = this.http.get("/api/count").subscribe((count) => {
            this.count = count
        })

        onDestroy(() => {
            sub.unsubscribe()
        })
    }

    ngOnConnect() {
        const asyncLogger = inject(AsyncLogger)

        watchEffect(() => {
            const cancel  = asyncLogger.logAfterDelay(this.count, 500)

            onInvalidate(() => {
                cancel() // called each time watchEffect deps change
            })
        })
    }
}

Features

  • allow onInvalidate and onDestroy in component methods

  • add side effect invalidation callbacks

  • fall back to global injector when inject is called outside of component context

Bug fixes

  • prevent injection context leaking to injected tokens

Misc

  • add todomvc example app

9.1.0-rc.0

This release introduces a composition/hooks model based on Vue 3’s Composition API. This will replace the decorator API, which has been deprecated.

We can now use functional composition with context-aware hooks to execute reactive effects.

const MyConnectable = connectable<AppComponent>((context) => { // connectable provider injected with reactive context
    // inject(HttpClient) dependency injection allowed in setup
    afterViewInit(() => { // lifecycle hooks
        effect(() => {
            // return teardown logic
            // cleaned up when component destroyed or effect is invalidated
        })
    })

    // available hooks:
    // - OnChanges: fires every time a component property change is detected
    // - AfterViewInit: fires once when component is first mounted
    // - WhenRendered: fires every time the component view updated
    // - OnDestroy: fires once when the component is being destroyed
})

@Component({
    selector: "app-root",
    template: `
        <div>Count: {{ count }}</div>
    `,
    providers: [MyConnectable] // executed after ngOnConnect
})
export class AppComponent extends Connectable { // base class required
    @Input()
    count = 0 // state

    private http = inject(HttpClient) // dependency injection allowed in initializers

    incrementCount() { // method
        // inject(HttpClient) dependency injection allowed in methods
        this.count += 1
    }

    ngOnConnect() { // setup
        // inject(HttpClient) dependency injection allowed in setup

        effect(() => // basic effect, no tracking
            interval(1000).subscribe(() => this.incrementCount()) // increment count once per second
        )

        watchEffect(() => { // reactive effect, dependency tracking
            console.log(this.count) // logs count whenever it changes
        })
    }
}

Features

  • use IterableDiffers for effect invalidation

  • add utils, add effect options, create untracked effect separate to watchEffect

  • allow inject() inside component methods

  • allow inject() inside property initializers

  • add connectable hook

  • add ngOnConnect hook

  • throw error when injecting outside of a valid injection context

  • add experimental composition api

Bug fixes

  • fix reactive factory

  • fix change detection, dependency injection

  • fix circular deps, initial change detection, create test component

  • fix memory leak

  • tap ngDoCheck lifecycle hook in effects scheduler

  • update changelog

  • fix types for typescript 3.8

  • fix error when accessing reactive state outside injection context

Deprecations

  • deprecate decorator API

The decorator API will be removed and replaced by the composition API in 10.0.0.

Deprecated Symbols

  • Connect

  • HOST_INITIALIZER

  • Effect

  • State

  • Context

  • Observe

  • HostRef

  • EffectMetadata

  • EffectAdapter

  • CreateEffectAdapter

  • NextEffectAdapter

  • DefaultEffectOptions

  • BindEffectOptions

  • AssignEffectOptions

  • AdapterEffectOptions

  • EffectOptions

  • ObservableSources

  • CONNECT

  • effects

  • Effects

  • USE_EXPERIMENTAL_RENDER_API

  • changes

  • latest

  • ViewRenderer

Misc

  • upgrade workspace

  • update readme

BREAKING CHANGES

The composition API relies on ES6 Proxy objects to create the proper execution context for connected components. This means dropping support for older browsers that don’t support them.

9.0.7

Bug fixes

  • ensure reactive state is updated when inputs change

9.0.6

Bug fixes

  • fix types for TypeScript 3.8

9.0.5

Bug fixes

  • fix bug caused by importing BrowserAnimationsModule (closes #6)

9.0.4

Bug fixes

  • fix assignment to effect bindings with union types

9.0.3

Bug fixes

  • fix typings for changes operator

9.0.2

Bug fixes

  • improve effect adapter typings

  • fix options for effect adapters that supply non-object arguments

9.0.1

No changes

9.0.0

Features

  • allow effect adapters to invoke effects and customise their arguments

BREAKING CHANGES

Effect adapters that implement the CreateEffectAdapter interface now receive the whole effect function as an argument instead of the invoked return value. This means effect adapters can take full control of the effect and supply the effect function with arbitrary arguments, invoke the function multiple times, etc.

Before

@Injectable()
export class MyAdapter implements EffectAdapter<number> {
    create(value: Observable<number>, metadata: EffectMetadata) {
        return value.pipe(
            delay(500)
        )
    }
    next(value: number) {
        console.log(value)
    }
}

After

type EffectFn = (state: State<any>, customArg: string) => Observable<number>

@Injectable()
export class MyAdapter implements EffectAdapter<EffectFn> {
    constructor(private hostRef: HostRef) {}

    create(effectFn: EffectFn, metadata: EffectMetadata) {
        return effectFn(this.hostRef.state, "CUSTOM_ARG")
    }

    next(value: number) {
        console.log(value)
    }
}

9.0.0-rc.6

Features

  • allow effects to run in modules

  • allow adapters to transform effects

  • allow effects to bind host emitters

  • query hostRef outside of effects loop

Bug fixes

  • don’t obfuscate errors in local effect providers

Misc

  • add MapStateToProps example

9.0.0-rc.5

Features

  • effects no longer need to be provided with effects()

  • rework effects() as an optional provider to configure defaults

  • remove HOST_EFFECTS provider

  • add Effects provider as a replacement for effects() and HOST_EFFECTS

Bug fixes

  • fix typed metadata in effect adapters

  • enforce return types when using effect adapters

  • workaround for InjectFlags.Self (#3)

  • check if view destroyed before marking view dirty

Misc

  • refactor effect explorer

  • add tests for effect parameters

BREAKING CHANGES

effects() is now only used to optionally configure default options. To run effects, provide the Effects token along with any other effect providers. Host effects only need the Effects token to run.

Before

@Component({
    providers: [effects([MyEffects, ...etc]), MyAdapter] // or [HOST_EFFECTS]
})
export class AppComponent {
    @Effect(MyAdapter)
    hostEffect() {}

    constructor(connect: Connect) {
        connect(this)
    }
}

After

@Component({
    providers: [Effects, MyEffects, MyAdapter, ...etc] // or [Effects]
})
export class AppComponent {
    @Effect(MyAdapter)
    hostEffect() {}

    constructor(connect: Connect) {
        connect(this)
    }
}

Only effects provided at the same level as the component or directive will be executed. Effects are not inherited from parent injectors and must be provided in every component that uses it.

9.0.0-rc.4

Features

  • add paramater decorators for State, Context and Observe

  • refactor effect types to support additional use cases

class AppEffects {
    @Effect()
    incrementCount(@Context() context: Context<AppState>) {}
}

Misc

  • remove latestFrom and roll it into changes

  • use proxy in prod if supported

9.0.0-rc.3

  • export missing tokens and tweak defaults

The default value of markDirty will now be true if the effect configures a bind or assign option. This is a better default in most cases, and can be configured by setting @Effect("prop", { markDirty: false }).

9.0.0-rc.2

Features

  • expose experimental connect API

9.0.0-rc.1

Features

  • add experimental global connect function

  • add host observer as third argument to effect methods

Misc

  • return cached metadata for already seen effect tokens

  • create effects in effect runner instead of explorer

  • create adapter in effect runner instead of explorer

  • make markDirty calls synchronous unless in noop zone

  • reduce usage of rxjs operators

  • updated docs

BREAKING CHANGES

  • remove createEffect factory

  • rename EffectHandler to EffectAdapter

  • EffectAdapter arguments are now just value and metadata. Options are now accessed through metadata.options

  • update peer Angular dependencies to v9.0.0. Will backport to v8.0.0 when need arises

9.0.0-beta.10

Features

  • add HostEmitter type for binding template/host events

Bug fixes

  • fix unsubscribe errors

9.0.0-beta.9

Misc

  • minor code refactor

HostRef property instance renamed to context

9.0.0-beta.8

Features

  • better error reporting in dev mode when attempting to use uninitialised state in effects.

  • add observable state to HostRef

HostRef now contains references to the observable state of the component or directive it is attached to. See docs for more information.

9.0.0-beta.7

Bug fixes

  • fix more adapter effect types

9.0.0-beta.6

Bug fixes

  • fix adapter effect types

9.0.0-beta.5

Bug fixes

  • remove effect metadata cache

9.0.0-beta.4

Features

  • adapters now receive effect metadata as a third argument

Bug fixes

  • fix change detection bug

9.0.0-beta.3

Misc

  • refactor internals for better code flow

9.0.0-beta.2

Features

  • performance improvements

  • add experimental zoneless event manager

Bug fixes

  • fix max call stack errors

9.0.0-beta.1

Features

  • defer state object creation until effect is called

State is proxied in dev mode to intercept and report uninitialised property access eg. state.viewChildren. This change allows Angular to bind properties before the state object is created by setting whenRendered: true.

9.0.0-beta.0

Initial release