Skip to content

Commit de63285

Browse files
committed
feat(core): refactor HOC: *compose, *return; added HOC: *nest, *renameProp, *setProps; implement ComposedView<C>
All behaviors can be changed later. ComposedView<C> Is similar between ViewContainerRef, TemplateRef<C>, EmbededViewRef<C>, and can be used by standard rendering directives. ComposeDirective Has selector [compose]. Will return enhancer. ReturnDirective Has selector [return]. Will rewrite context. RenamePropDirective Has selector [renameProp]. Will change name of parent context prop. SetPropsDirective Has selector [setProps]. Similar like ReturnDirective, but update parent context. NestDirective Has selector [nest]. Similar like ComposeDirectve, but returns ComposedView<C>. BREAKING CHANGE: Now *compose returns enhancer
1 parent 26891bf commit de63285

22 files changed

Lines changed: 589 additions & 167 deletions

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 40 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,72 @@
1-
import { Directive, Input, ViewContainerRef, TemplateRef, EmbeddedViewRef, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
1+
import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
2+
import { ComposedContext, ComposedView } from '../tools';
23

3-
const COMPOSED_CONTEXT_TOKEN = '[COMPOSED_CONTEXT_TOKEN]';
4-
5-
interface ComposedContext {
6-
$implicit: any;
7-
templateRefs: TemplateRef<ComposedContext>[];
8-
9-
[ key: string ]: any;
4+
interface ComposeContext {
5+
$implicit: ComposeFn<any>;
6+
compose: ComposeFn<any>;
107
}
118

12-
class ComposedView {
13-
14-
private context: ComposedContext = {
15-
$implicit: null,
16-
templateRefs: [],
17-
[ COMPOSED_CONTEXT_TOKEN ]: COMPOSED_CONTEXT_TOKEN
18-
};
19-
private viewRef: EmbeddedViewRef<ComposedContext>;
20-
21-
constructor(private viewContainerRef: ViewContainerRef) {}
22-
23-
render($implicit: any, templateRefs: TemplateRef<ComposedContext>[]) {
24-
this.viewContainerRef.clear();
25-
this.context.$implicit = $implicit;
26-
27-
const [ templateRef, ...tail ] = templateRefs;
28-
this.context.templateRefs = tail;
29-
this.viewRef =
30-
this.viewContainerRef.createEmbeddedView(templateRef, this.context);
31-
}
32-
33-
update($implicit: any) {
34-
if (this.viewRef) {
35-
this.context.$implicit = $implicit;
36-
this.viewRef.markForCheck();
37-
}
38-
}
39-
40-
destroy() {
41-
this.viewContainerRef.clear();
42-
if (this.viewRef) {
43-
this.viewRef.destroy();
44-
this.viewRef = null;
45-
}
46-
}
47-
}
9+
type ComposeFn<T> = (templateRef: TemplateRef<T> | ComposedView<T>) => ComposedView<T>;
4810

4911
@Directive({ selector: '[compose]' })
5012
export class ComposeDirective implements OnChanges, OnDestroy {
51-
@Input() composeOf: TemplateRef<ComposedContext>[];
52-
@Input() composeUse: any;
5313

54-
private get templateRefs(): TemplateRef<ComposedContext>[] {
55-
return this.composeOf;
56-
}
14+
@Input() compose: TemplateRef<any>[];
15+
@Input() composeOf: TemplateRef<any>[];
5716

58-
private composedView: ComposedView;
17+
private context: ComposeContext = { $implicit: null, compose: null };
18+
private viewRef: EmbeddedViewRef<ComposeContext>;
5919

6020
constructor(
61-
private templateRef: TemplateRef<ComposedContext>,
62-
viewContainerRef: ViewContainerRef
63-
) {
64-
this.composedView = new ComposedView(viewContainerRef);
65-
}
21+
private templateRef: TemplateRef<ComposeContext>,
22+
private viewContainerRef: ViewContainerRef
23+
) {}
6624

6725
ngOnChanges(changes: SimpleChanges) {
68-
if (changes.composeOf && this.composeOf) {
69-
return this.render();
70-
}
71-
72-
if (changes.composeUse && this.composeOf) {
73-
return this.update();
26+
if ('compose' in changes) {
27+
this.onTemplateRefsDidChanged(this.compose, changes.compose.previousValue);
7428
}
7529

76-
if (changes.composeOf && !this.composeOf) {
77-
return this.destroy();
30+
if ('composeOf' in changes) {
31+
this.onTemplateRefsDidChanged(this.composeOf, changes.composeOf.previousValue);
7832
}
7933
}
8034

8135
ngOnDestroy() {
8236
this.destroy();
8337
}
8438

85-
private render() {
86-
const templateRefs = [ ...this.templateRefs, this.templateRef ];
87-
this.composedView.render(this.composeUse, templateRefs);
88-
}
89-
90-
private update() {
91-
this.composedView.update(this.composeUse);
92-
}
93-
94-
private destroy() {
95-
this.composedView.destroy();
96-
}
97-
}
98-
99-
@Directive({ selector: '[return]' })
100-
export class ReturnDirective implements OnDestroy {
101-
@Input() set return(value: any) {
102-
if (this.templateRefsChanged) {
103-
this.previousTemplateRefs = this.templateRefs;
104-
this.composedView.render(value, this.templateRefs);
105-
} else {
106-
this.composedView.update(value);
39+
private onTemplateRefsDidChanged(current: TemplateRef<any>[], previous: TemplateRef<any>[]): void {
40+
if (!this.viewRef) {
41+
return this.create(current);
10742
}
108-
}
109-
110-
private composedView: ComposedView;
111-
112-
private previousTemplateRefs: TemplateRef<ComposedContext>[];
11343

114-
private get templateRefs(): TemplateRef<ComposedContext>[] {
115-
const view = (this.viewContainerRef.injector as any).view;
116-
const context: ComposedContext = findParentContext(view);
117-
return context.templateRefs;
44+
if (this.viewRef.destroyed || current !== previous) {
45+
this.destroy();
46+
return this.onTemplateRefsDidChanged(current, null);
47+
}
11848
}
11949

120-
private get templateRefsChanged(): boolean {
121-
return this.previousTemplateRefs !== this.templateRefs;
50+
private create(templateRefs) {
51+
this.context.compose = this.context.$implicit = this.createComposeFn(templateRefs);
52+
this.viewRef =
53+
this.viewContainerRef.createEmbeddedView(this.templateRef, this.context);
12254
}
12355

124-
constructor(private viewContainerRef: ViewContainerRef) {
125-
this.composedView = new ComposedView(viewContainerRef);
126-
}
56+
private destroy() {
57+
if (this.viewRef && !this.viewRef.destroyed) {
58+
this.viewRef.destroy();
59+
}
12760

128-
ngOnDestroy() {
129-
this.composedView.destroy();
61+
this.viewRef = null;
13062
}
13163

132-
}
133-
134-
function findParentContext(view: any): ComposedContext {
135-
const context: any = view.context;
136-
137-
if (isComposedContext(context)) {
138-
return context;
64+
private createComposeFn(templateRefs: TemplateRef<any>[]): ComposeFn<any> {
65+
return (templateRef: TemplateRef<any> | ComposedView<any>): ComposedView<any> => {
66+
return new ComposedView<any>(
67+
this.viewContainerRef,
68+
[ ...templateRefs, templateRef ]
69+
);
70+
};
13971
}
140-
141-
return findParentContext(view.parent);
142-
}
143-
144-
function isComposedContext(context: any): context is ComposedContext {
145-
return context && context[ COMPOSED_CONTEXT_TOKEN ];
14672
}

projects/platform/src/lib/directives/cookies.directive.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ export class CookiesDirective implements OnChanges, OnDestroy {
7272

7373
ngOnChanges(changes: SimpleChanges) {
7474
const strategy: CookiesStrategy = this.findStrategy(changes);
75-
7675
if (strategy) {
7776
this.execute(strategy);
7877
}

projects/platform/src/lib/directives/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { AsyncDirective } from './async.directive';
2-
import { ComposeDirective, ReturnDirective } from './compose.directive';
2+
import { ComposeDirective } from './compose.directive';
33
import { CookiesDirective } from './cookies.directive';
44
import { HttpDirective } from './http.directive';
55
import { LazyDirective } from './lazy.directive';
66
import { InitDirective } from './init.directive';
7+
import { NestDirective } from './nest.directive';
8+
import { RenamePropDirective } from './rename-prop.directive';
9+
import { ReturnDirective } from './return.directive';
710
import { RouteDirective } from './route.directive';
11+
import { SetPropsDirective } from './set-props.directive';
812
import { TimeoutDirective } from './timeout.directive';
913

1014
export const DIRECTIVES = [
@@ -14,8 +18,11 @@ export const DIRECTIVES = [
1418
HttpDirective,
1519
LazyDirective,
1620
InitDirective,
21+
NestDirective,
22+
RenamePropDirective,
1723
ReturnDirective,
1824
RouteDirective,
25+
SetPropsDirective,
1926
TimeoutDirective
2027
];
2128

@@ -25,5 +32,9 @@ export * from './cookies.directive';
2532
export * from './http.directive';
2633
export * from './lazy.directive';
2734
export * from './init.directive';
35+
export * from './nest.directive';
36+
export * from './rename-prop.directive';
37+
export * from './return.directive';
2838
export * from './route.directive';
39+
export * from './set-props.directive';
2940
export * from './timeout.directive';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
2+
import { ComposedView } from '../tools';
3+
4+
interface NestContext {
5+
$implicit: any;
6+
nest: any;
7+
}
8+
9+
@Directive({ selector: '[nest]' })
10+
export class NestDirective implements OnChanges, OnDestroy {
11+
12+
@Input() nest: TemplateRef<NestContext>[];
13+
@Input() nestOf: TemplateRef<NestContext>[];
14+
15+
private context: NestContext = { $implicit: null, nest: null };
16+
private viewRef: EmbeddedViewRef<NestContext>;
17+
18+
constructor(
19+
private templateRef: TemplateRef<NestContext>,
20+
private viewContainerRef: ViewContainerRef
21+
) {}
22+
23+
ngOnChanges(changes: SimpleChanges) {
24+
if ('nest' in changes) {
25+
this.onTemplatesDidChanged(this.nest, changes.nest.previousValue);
26+
}
27+
28+
if ('nestOf' in changes) {
29+
this.onTemplatesDidChanged(this.nestOf, changes.nestOf.previousValue);
30+
}
31+
}
32+
33+
ngOnDestroy() {
34+
this.destroy();
35+
}
36+
37+
private onTemplatesDidChanged(current: TemplateRef<NestContext>[], previous: TemplateRef<NestContext>[]) {
38+
if (!this.context.$implicit) {
39+
return current && this.create(current);
40+
}
41+
42+
if (current !== previous) {
43+
this.destroy();
44+
this.onTemplatesDidChanged(current, null);
45+
}
46+
}
47+
48+
private create(templateRefs: TemplateRef<NestContext>[]) {
49+
this.context.$implicit = this.context.nest = new ComposedView<NestContext>(this.viewContainerRef, templateRefs);
50+
this.viewRef =
51+
this.viewContainerRef.createEmbeddedView(this.templateRef, this.context);
52+
}
53+
54+
private update(templateRefs: TemplateRef<NestContext>[]) {
55+
this.context.$implicit = this.context.nest = new ComposedView<NestContext>(this.viewContainerRef, templateRefs);
56+
this.viewRef.markForCheck();
57+
}
58+
59+
private destroy() {
60+
this.viewContainerRef.clear();
61+
if (this.viewRef) {
62+
this.viewRef.destroy();
63+
this.viewRef = null;
64+
}
65+
}
66+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Directive, Input, OnChanges, OnDestroy, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
2+
import { ComposedView } from '../tools';
3+
import { omit } from '../tools';
4+
5+
interface RenamePropContext {};
6+
7+
@Directive({ selector: '[renameProp]' })
8+
export class RenamePropDirective implements OnChanges, OnDestroy {
9+
10+
@Input() renameProp: string;
11+
@Input() renamePropTo: string;
12+
13+
private context: RenamePropContext = {};
14+
private composedView: ComposedView<RenamePropContext>;
15+
16+
constructor(
17+
private templateRef: TemplateRef<RenamePropContext>,
18+
private viewContainerRef: ViewContainerRef
19+
) {}
20+
21+
ngOnChanges(changes: SimpleChanges) {
22+
if ('renameProp' in changes && 'renamePropTo' in changes) {
23+
this.onRenameDidChanged(
24+
this.renameProp, this.renamePropTo,
25+
changes.renameProp.previousValue, changes.renamePropTo.previousValue
26+
);
27+
}
28+
}
29+
30+
ngOnDestroy() {
31+
this.destroy();
32+
}
33+
34+
private onRenameDidChanged(from: string, to: string, fromPrevious: string, toPrevious: string) {
35+
if (!this.composedView) {
36+
this.create();
37+
}
38+
39+
if (from !== fromPrevious || to !== toPrevious) {
40+
this.rename(from, to);
41+
}
42+
}
43+
44+
private create() {
45+
this.composedView = new ComposedView<RenamePropContext>(this.viewContainerRef);
46+
this.composedView.createEmbeddedView(this.context);
47+
}
48+
49+
private rename(from: string, to: string) {
50+
this.composedView.updateContext((context, parent) => {
51+
return Object.assign(
52+
context,
53+
omit(parent, [ from ]),
54+
{ [ to ]: parent[ from ] }
55+
);
56+
});
57+
}
58+
59+
private destroy() {
60+
this.viewContainerRef.clear();
61+
if (this.composedView) {
62+
this.composedView.destroy();
63+
this.composedView = null;
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)