Skip to content

Commit 7d019db

Browse files
feat(overmind-angular): decorator required, state scope transfer
1 parent 8a841d2 commit 7d019db

File tree

6 files changed

+77
-56
lines changed

6 files changed

+77
-56
lines changed

packages/node_modules/overmind-angular/src/index.ts

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Overmind, EventType } from 'overmind'
22
import { BehaviorSubject, Observable } from 'rxjs'
33
import { map } from 'rxjs/operators'
4+
import { IS_PROXY } from 'proxy-state-tree'
45

56
class ServiceBase {}
67

@@ -10,27 +11,61 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
1011
let nextComponentId = 0
1112

1213
const Track = (Component) => {
13-
if (IS_PRODUCTION) {
14-
return
15-
}
16-
1714
const componentId = nextComponentId++
1815
let componentInstanceId = 0
1916

2017
function findService(target) {
2118
return Object.keys(target).find((key) => target[key] instanceof ServiceBase)
2219
}
2320

24-
const targetNgAfterViewInit = Component.prototype.ngAfterViewInit
25-
Component.prototype.ngAfterViewInit = function() {
21+
if (!IS_PRODUCTION) {
22+
const targetNgOnInit = Component.prototype.ngOnInit
23+
Component.prototype.ngOnInit = function() {
24+
if (
25+
!Component['__annotations__'][0] ||
26+
Component['__annotations__'][0].changeDetection !== 0
27+
) {
28+
throw new Error(
29+
'OVERMIND: You have to add ChangeDetectionStrategy.OnPush on component ' +
30+
Component.name
31+
)
32+
}
33+
targetNgOnInit && targetNgOnInit.call(this)
34+
}
35+
const targetNgAfterViewInit = Component.prototype.ngAfterViewInit
36+
Component.prototype.ngAfterViewInit = function() {
37+
const service = this[findService(this)]
38+
39+
if (!service) {
40+
throw new Error(
41+
'OVERMIND - You have not added the Overmind service to component ' +
42+
Component.name
43+
)
44+
}
45+
46+
service.addComponent({
47+
componentId,
48+
componentInstanceId: componentInstanceId++,
49+
name: Component.name,
50+
})
51+
52+
targetNgAfterViewInit && targetNgAfterViewInit.call(this)
53+
}
54+
}
55+
56+
const targetNgOnChanges = Component.prototype.ngOnChanges
57+
Component.prototype.ngOnChanges = function(changes) {
2658
const service = this[findService(this)]
27-
service.addComponent({
28-
componentId,
29-
componentInstanceId: componentInstanceId++,
30-
name: Component.name,
31-
})
3259

33-
targetNgAfterViewInit && targetNgAfterViewInit.call(this)
60+
for (let key in changes) {
61+
if (changes[key].currentValue[IS_PROXY]) {
62+
this[key] = service.tree.master.rescope(
63+
changes[key].currentValue,
64+
service.tree
65+
)
66+
}
67+
}
68+
targetNgOnChanges && targetNgOnChanges.call(this, changes)
3469
}
3570
}
3671

@@ -49,7 +84,7 @@ export function createService<App extends Overmind<any>>(
4984
overmind: App
5085
): IService<App> {
5186
return class Service extends ServiceBase {
52-
static Track
87+
static Track = Track
5388
private tree: any
5489
private state$: Observable<any>
5590
private subject: BehaviorSubject<any>
@@ -69,8 +104,24 @@ export function createService<App extends Overmind<any>>(
69104
this.addMutationListener = this.overmind.addMutationListener
70105

71106
this.tree.track(this.onUpdate)
107+
108+
if (!IS_PRODUCTION) {
109+
;(window['__zone_symbol__setTimeout'] || setTimeout)(() => {
110+
if (!this.componentDetails) {
111+
throw new Error(
112+
'OVERMIND - You have added a service without adding the Track decorator'
113+
)
114+
}
115+
})
116+
}
72117
}
73118
private addComponent(componentDetails) {
119+
if (this.componentDetails) {
120+
throw new Error(
121+
'OVERMIND - This service is already instantiated, you have to provide it as well with providers: [OvermindService] on ' +
122+
this.componentDetails.name
123+
)
124+
}
74125
this.componentDetails = componentDetails
75126
this.overmind.eventHub.emitAsync(EventType.COMPONENT_ADD, {
76127
componentId: componentDetails.componentId,

packages/overmind-website/examples/guide/usingovermindwithangular/connect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { OvermindService } from '../overmind'
3939
providers: [OvermindService],
4040
changeDetection: ChangeDetectionStrategy.OnPush
4141
})
42+
@OvermindService.Track
4243
export class AppComponent {
4344
state$ = this.overmind.select()
4445
actions: this.overmind.actions

packages/overmind-website/examples/guide/usingovermindwithangular/connect_custom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { OvermindService } from '../overmind'
1515
providers: [OvermindService],
1616
changeDetection: ChangeDetectionStrategy.OnPush
1717
})
18+
@OvermindService.Track
1819
export class AppComponent {
1920
state$ = this.overmind.select(state => state.admin)
2021
actions: this.overmind.actions.admin

packages/overmind-website/examples/guide/usingovermindwithangular/passprop.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ import { Todo } from '../overmind/state'
88
99
@Component({
1010
selector: 'todos-todo',
11-
changeDetection: ChangeDetectionStrategy.OnPush,
1211
template: \`
1312
<li ngIf="todo$ | async as todo">{{ todo.title }}</li>
14-
\`
13+
\`,
14+
changeDetection: ChangeDetectionStrategy.OnPush,
1515
})
16-
@connect()
16+
@OvermindService.Track
1717
export class TodoComponent {
18-
@Input() index: number
19-
todo$ = this.overmind.select(state => state.todos[this.index])
18+
@Input() todo: Todo
2019
constructor(private overmind: OvermindService) {}
2120
}
2221
`,
@@ -31,13 +30,12 @@ import { OvermindService } from '../overmind'
3130
selector: 'todos-list',
3231
template: \`
3332
<ul *ngIf="state$ | async as state">
34-
<todos-todo
35-
*ngFor="let todo of state.todos; index as i;"
36-
[index]="i"
37-
></todos-todo>
33+
<todos-todo *ngFor="let todo of state.todos;" [todo]="todo"></todos-todo>
3834
</ul>
39-
\`
35+
\`,
36+
changeDetection: ChangeDetectionStrategy.OnPush
4037
})
38+
@OvermindService.Track
4139
export class ListComponent {
4240
state$ = this.overmind.select()
4341
constructor(private overmind: OvermindService)

packages/overmind-website/examples/guide/usingovermindwithangular/track.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/overmind-website/guides/beginner/07_usingovermindwithangular.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,22 @@ Let us have a look at how you create the service an expose it to components:
1212
h(Example, { name: "guide/usingovermindwithangular/connect" })
1313
```
1414

15-
You can also expose parts of the configuration:
15+
The **service** is responsible for exposing the configuration of your application and manage notifying the component when tracked state changes. The **Track** decorator primarily helps you with debugging, but also manages transferring tracking between components when it is passed as input. If you forget to any of these parts Overmind will yell at you.
1616

1717
```marksy
1818
h(Example, { name: "guide/usingovermindwithangular/connect_custom" })
1919
```
2020

2121
You can now access the **admin** state and actions directly with **state** and **actions**.
2222

23-
## Track components
24-
25-
Optionally you can also track the component itself. The **service** exposes a decorator called **Track**. This will allow you to follow what components are looking at state, what exact state they are looking at and when they update, using the devtools.
26-
27-
```marksy
28-
h(Example, { name: "guide/usingovermindwithangular/track" })
29-
```
30-
3123
## Rendering
3224

33-
When you connect Overmind to your component and expose state you do not have to think about how much state you expose. The exact state that is being accessed in the template is the state that will be tracked. That means you can expose all the state of the application to all your components without worrying about performance.
25+
When you connect Overmind to your component and expose state you do not have to think about how much state you expose. The exact state that is being accessed in the template is the state that will be tracked. That means you can expose all the state of the application to all your components without worrying about performance.
3426

3527

3628
## Passing state as input
3729

38-
When you pass state objects or arrays as input to a child component that state will by default be tracked on the component passing it a long, which you can also see in the devtools. If you want to effectively pass state as input you should rather pass a reference so that the child component can connect to it.
30+
When you pass state objects or arrays as input to a child component that state will by default be tracked on the component passing it a long, which you can also see in the devtools. By just adding the **service** to the child components as well this tracking is transferred to the child component.
3931

4032
```marksy
4133
h(Example, { name: "guide/usingovermindwithangular/passprop" })

0 commit comments

Comments
 (0)