Skip to content

Commit 3f41dc7

Browse files
committed
feat(core): added *compose directive
ComposeDirective provides tempalte composition An Example: <ng-template #pluck let-object> <ng-container *return="object.hello"></ng-container> </ng-template> <ng-container *compose="let hello of [ pluck ] use { hello: 'World!' }"> {{ hello }} // World! </ng-container>
1 parent b83c174 commit 3f41dc7

3 files changed

Lines changed: 175 additions & 1 deletion

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { Directive, Input, ViewContainerRef, TemplateRef, EmbeddedViewRef, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
2+
3+
const COMPOSED_CONTEXT_TOKEN = '[COMPOSED_CONTEXT_TOKEN]';
4+
5+
interface ComposedContext {
6+
$implicit: any;
7+
templateRefs: TemplateRef<ComposedContext>[];
8+
[key: string]: any;
9+
}
10+
11+
class ComposedView {
12+
13+
private context: ComposedContext = {
14+
$implicit: null,
15+
templateRefs: [],
16+
[COMPOSED_CONTEXT_TOKEN]: COMPOSED_CONTEXT_TOKEN
17+
};
18+
private viewRef: EmbeddedViewRef<ComposedContext>;
19+
20+
constructor(private viewContainerRef: ViewContainerRef) {}
21+
22+
render($implicit: any, templateRefs: TemplateRef<ComposedContext>[]) {
23+
this.viewContainerRef.clear();
24+
this.context.$implicit = $implicit;
25+
26+
const [ templateRef, ...tail ] = templateRefs;
27+
this.context.templateRefs = tail;
28+
this.viewRef =
29+
this.viewContainerRef.createEmbeddedView(templateRef, this.context);
30+
}
31+
32+
update($implicit: any) {
33+
if (this.viewRef) {
34+
this.context.$implicit = $implicit;
35+
this.viewRef.markForCheck();
36+
}
37+
}
38+
39+
destroy() {
40+
this.viewContainerRef.clear();
41+
}
42+
}
43+
44+
@Directive({ selector: '[compose]' })
45+
export class ComposeDirective implements OnChanges, OnDestroy {
46+
@Input() composeOf: TemplateRef<ComposedContext>[];
47+
@Input() composeUse: any;
48+
49+
private get templateRefs(): TemplateRef<ComposedContext>[] {
50+
return this.composeOf;
51+
}
52+
53+
private composedView: ComposedView;
54+
55+
constructor(
56+
private templateRef: TemplateRef<ComposedContext>,
57+
viewContainerRef: ViewContainerRef
58+
) {
59+
this.composedView = new ComposedView(viewContainerRef);
60+
}
61+
62+
ngOnChanges(changes: SimpleChanges) {
63+
if (changes.composeOf && this.composeOf) {
64+
return this.render();
65+
}
66+
67+
if (changes.composeUse && this.composeOf) {
68+
return this.update();
69+
}
70+
71+
if (changes.composeOf && !this.composeOf) {
72+
return this.destroy();
73+
}
74+
}
75+
76+
ngOnDestroy() {
77+
this.destroy();
78+
}
79+
80+
private render() {
81+
const templateRefs = [...this.templateRefs, this.templateRef ];
82+
this.composedView.render(this.composeUse, templateRefs);
83+
}
84+
85+
private update() {
86+
this.composedView.update(this.composeUse);
87+
}
88+
89+
private destroy() {
90+
this.composedView.destroy();
91+
}
92+
}
93+
94+
@Directive({ selector: '[return]' })
95+
export class ReturnDirective implements OnDestroy {
96+
@Input() set return(value: any) {
97+
if (this.templateRefsChanged) {
98+
this.previousTemplateRefs = this.templateRefs;
99+
this.composedView.render(value, this.templateRefs);
100+
} else {
101+
this.composedView.update(value);
102+
}
103+
}
104+
105+
private composedView: ComposedView;
106+
107+
private previousTemplateRefs: TemplateRef<ComposedContext>[];
108+
private get templateRefs(): TemplateRef<ComposedContext>[] {
109+
const view = (this.viewContainerRef.injector as any).view;
110+
const context: ComposedContext = findParentContext(view);
111+
return context.templateRefs;
112+
}
113+
private get templateRefsChanged(): boolean {
114+
return this.previousTemplateRefs !== this.templateRefs;
115+
}
116+
117+
constructor(private viewContainerRef: ViewContainerRef) {
118+
this.composedView = new ComposedView(viewContainerRef);
119+
}
120+
121+
ngOnDestroy() {
122+
this.composedView.destroy();
123+
}
124+
125+
}
126+
127+
function findParentContext(view: any): ComposedContext {
128+
const context: any = view.context;
129+
130+
if (isComposedContext(context)) {
131+
return context;
132+
}
133+
134+
return findParentContext(view.parent);
135+
}
136+
137+
function isComposedContext(context: any): context is ComposedContext {
138+
return context && context[COMPOSED_CONTEXT_TOKEN];
139+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ import { HttpDirective } from './http.directive';
22
import { RouteDirective } from './route.directive';
33
import { InitDirective } from './init.directive';
44
import { TimeoutDirective } from './timeout.directive';
5+
import { ComposeDirective, ReturnDirective } from './compose.directive';
56

67
export const list = [
78
HttpDirective,
89
RouteDirective,
910
InitDirective,
10-
TimeoutDirective
11+
TimeoutDirective,
12+
ComposeDirective,
13+
ReturnDirective
1114
];
1215

1316
export * from './http.directive';
1417
export * from './route.directive';
1518
export * from './init.directive';
1619
export * from './timeout.directive';
20+
export * from './compose.directive';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
11
<ng-container *init="let myvar of 'LOL'">
22
{{ myvar }}
3+
</ng-container>
4+
5+
6+
<ng-template #fetchUser let-username>
7+
<ng-container *http="let user get 'https://api.github.com/users/' + username">
8+
<ng-container *ngIf="user">
9+
<ng-container *return="user"></ng-container>
10+
</ng-container>
11+
</ng-container>
12+
</ng-template>
13+
14+
<ng-template #getFieldOrgUrl let-user>
15+
<ng-container *return="user['organizations_url']"></ng-container>
16+
</ng-template>
17+
18+
<ng-template #fetchOrg let-url>
19+
<ng-container *http="let org get url">
20+
<ng-container *ngIf="org">
21+
<ng-container *return="org"></ng-container>
22+
</ng-container>
23+
</ng-container>
24+
</ng-template>
25+
26+
<ng-container *compose="
27+
let org of [
28+
fetchUser,
29+
getFieldOrgUrl,
30+
fetchOrg
31+
] use 'thekiba'
32+
">
33+
{{ org | json }}
334
</ng-container>

0 commit comments

Comments
 (0)