Skip to content

Commit 3070a71

Browse files
committed
feat(portal): add initial portal foundation
1 parent 37e4ef8 commit 3070a71

File tree

13 files changed

+749
-18
lines changed

13 files changed

+749
-18
lines changed

src/core/portal/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Portals
2+
3+
### Overview
4+
5+
A `Portal `is a piece of UI that can be dynamically rendered to an open slot on the page.
6+
7+
The "piece of UI" can be either a `Component` or a `TemplateRef`.
8+
9+
The "open slot" is a `PortalHost`.
10+
11+
Portals and PortalHosts are low-level building blocks that other concepts, such as overlays, can
12+
be built upon.
13+
14+
##### `Portal<T>`
15+
| Method | Description |
16+
| --- | --- |
17+
| `attach(PortalHost): Promise<T>` | Attaches the portal to a host. |
18+
| `detach(): Promise<void>` | Detaches the portal from its host. |
19+
| `isAttached: boolean` | Whether the portal is attached. |
20+
21+
##### `PortalHost`
22+
| Method | Description |
23+
| --- | --- |
24+
| `attach(Portal): Promise<void>` | Attaches a portal to the host. |
25+
| `detach(): Promise<void>` | Detaches the portal from the host. |
26+
| `dispose(): Promise<void>` | Permanently dispose the host. |
27+
| `hasAttached: boolean` | Whether a portal is attached to the host. |
28+
29+
30+
31+
32+
### Using portals
33+
34+
35+
36+
##### `TemplatePortalDirective`
37+
Used to get a portal from a `<template>`. `TemplatePortalDirectives` *is* a `Portal`.
38+
39+
Usage:
40+
```html
41+
<template portal>
42+
<p>The content of this template is captured by the portal.</p>
43+
</template>
44+
45+
<!-- OR -->
46+
47+
<!-- This result here is identical to the syntax above -->
48+
<p *portal>
49+
The content of this template is captured by the portal.
50+
</p>
51+
```
52+
53+
A component can use `@ViewChild` or `@ViewChildren` to get a reference to a
54+
`TemplatePortalDiective`.
55+
56+
##### `ComponentPortal`
57+
Used to create a portal from a component type.
58+
59+
Usage:
60+
```ts
61+
this.userSettingsPortal = new ComponentPortal(UserSettingsComponent);
62+
```
63+
64+
65+
##### `PortalHostDirective`
66+
Used to add a portal host to a template. `PortalHostDirective` *is* a `PortalHost`.
67+
68+
Usage:
69+
```html
70+
<!-- Attaches the `userSettingsPortal` from the previous example. -->
71+
<template [portalHost]="userSettingsPortal"></template>
72+
```

src/core/portal/portal-directives.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {Portal} from './portal';
2+
import {TemplatePortal} from './portal';
3+
import {ComponentRef} from 'angular2/core';
4+
import {ComponentPortal} from './portal';
5+
import {Directive} from 'angular2/core';
6+
import {TemplateRef} from 'angular2/core';
7+
import {BasePortalHost} from './portal';
8+
import {DynamicComponentLoader} from 'angular2/core';
9+
import {ElementRef} from 'angular2/core';
10+
import {ViewContainerRef} from 'angular2/core';
11+
12+
13+
/**
14+
* Directive version of a `TemplatePortal`. Because the directive *is* a TemplatePortal,
15+
* the directive instance itself can be attached to a host, enabling declarative use of portals.
16+
*
17+
* Usage:
18+
* <template portal #greeting>
19+
* <p> Hello {{name}} </p>
20+
* </template>
21+
*/
22+
@Directive({
23+
selector: '[portal]',
24+
exportAs: 'portal',
25+
})
26+
export class TemplatePortalDirective extends TemplatePortal {
27+
constructor(templateRef: TemplateRef) {
28+
super(templateRef);
29+
}
30+
}
31+
32+
33+
/**
34+
* Directive version of a PortalHost. Because the directive *is* a PortalHost, portals can be
35+
* directly attached to it, enabling declarative use.
36+
*
37+
* Usage:
38+
* <template [portalHost]="greeting"></template>
39+
*/
40+
@Directive({
41+
selector: '[portalHost]',
42+
inputs: ['portal: portalHost']
43+
})
44+
export class PortalHostDirective extends BasePortalHost {
45+
/** The attached portal. */
46+
private portal_: Portal<any>;
47+
48+
constructor(
49+
private dynamicComponentLoader_: DynamicComponentLoader,
50+
private elementRef_: ElementRef,
51+
private viewContainerRef_: ViewContainerRef) {
52+
super();
53+
}
54+
55+
get portal(): Portal<any> {
56+
return this.portal_;
57+
}
58+
59+
set portal(p: Portal<any>) {
60+
this.replaceAttachedPortal_(p);
61+
}
62+
63+
/** Attach the given ComponentPortal to this PortlHost using the DynamicComponentLoader. */
64+
attachComponentPortal(portal: ComponentPortal): Promise<ComponentRef> {
65+
portal.setAttachedHost(this);
66+
67+
// If the portal specifies an origin, use that as the logical location of the component
68+
// in the application tree. Otherwise use the location of this PortalHost.
69+
let elementRef = portal.origin != null ? portal.origin : this.elementRef_;
70+
71+
// Typecast is necessary for Dart transpilation.
72+
return <Promise<ComponentRef>>
73+
this.dynamicComponentLoader_.loadNextToLocation(portal.component, elementRef)
74+
.then(ref => {
75+
this.setDisposeFn(() => ref.dispose());
76+
return ref;
77+
});
78+
}
79+
80+
/** Attach the given TemplatePortal to this PortlHost as an embedded View. */
81+
attachTemplatePortal(portal: TemplatePortal): Promise<Map<string, any>> {
82+
portal.setAttachedHost(this);
83+
84+
let viewRef = this.viewContainerRef_.createEmbeddedView(portal.templateRef);
85+
portal.locals.forEach((v, k) => viewRef.setLocal(k, v));
86+
this.setDisposeFn(() => this.viewContainerRef_.clear());
87+
88+
// TODO(jelbourn): return locals from view
89+
// Typecast is necessary for Dart transpilation.
90+
return <Promise<Map<string, any>>> Promise.resolve(new Map<string, any>());
91+
}
92+
93+
/** Detatches the currently attached Portal (if there is one) and attaches the given Portal. */
94+
private replaceAttachedPortal_(p: Portal<any>): void {
95+
let maybeDetach = this.hasAttached() ? this.detach() : Promise.resolve(null);
96+
97+
maybeDetach.then(_ => {
98+
if (p != null) {
99+
this.attach(p);
100+
this.portal_ = p;
101+
}
102+
});
103+
}
104+
}

0 commit comments

Comments
 (0)