portal.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. /**
  2. * @license
  3. * Copyright Google LLC All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import { ComponentFactoryResolver, Directive, EventEmitter, NgModule, Output, TemplateRef, ViewContainerRef } from '@angular/core';
  9. /**
  10. * @fileoverview added by tsickle
  11. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  12. */
  13. /**
  14. * Throws an exception when attempting to attach a null portal to a host.
  15. * \@docs-private
  16. * @return {?}
  17. */
  18. function throwNullPortalError() {
  19. throw Error('Must provide a portal to attach');
  20. }
  21. /**
  22. * Throws an exception when attempting to attach a portal to a host that is already attached.
  23. * \@docs-private
  24. * @return {?}
  25. */
  26. function throwPortalAlreadyAttachedError() {
  27. throw Error('Host already has a portal attached');
  28. }
  29. /**
  30. * Throws an exception when attempting to attach a portal to an already-disposed host.
  31. * \@docs-private
  32. * @return {?}
  33. */
  34. function throwPortalOutletAlreadyDisposedError() {
  35. throw Error('This PortalOutlet has already been disposed');
  36. }
  37. /**
  38. * Throws an exception when attempting to attach an unknown portal type.
  39. * \@docs-private
  40. * @return {?}
  41. */
  42. function throwUnknownPortalTypeError() {
  43. throw Error('Attempting to attach an unknown Portal type. BasePortalOutlet accepts either ' +
  44. 'a ComponentPortal or a TemplatePortal.');
  45. }
  46. /**
  47. * Throws an exception when attempting to attach a portal to a null host.
  48. * \@docs-private
  49. * @return {?}
  50. */
  51. function throwNullPortalOutletError() {
  52. throw Error('Attempting to attach a portal to a null PortalOutlet');
  53. }
  54. /**
  55. * Throws an exception when attempting to detach a portal that is not attached.
  56. * \@docs-private
  57. * @return {?}
  58. */
  59. function throwNoPortalAttachedError() {
  60. throw Error('Attempting to detach a portal that is not attached to a host');
  61. }
  62. /**
  63. * @fileoverview added by tsickle
  64. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  65. */
  66. /**
  67. * A `Portal` is something that you want to render somewhere else.
  68. * It can be attach to / detached from a `PortalOutlet`.
  69. * @abstract
  70. * @template T
  71. */
  72. class Portal {
  73. /**
  74. * Attach this portal to a host.
  75. * @param {?} host
  76. * @return {?}
  77. */
  78. attach(host) {
  79. if (host == null) {
  80. throwNullPortalOutletError();
  81. }
  82. if (host.hasAttached()) {
  83. throwPortalAlreadyAttachedError();
  84. }
  85. this._attachedHost = host;
  86. return (/** @type {?} */ (host.attach(this)));
  87. }
  88. /**
  89. * Detach this portal from its host
  90. * @return {?}
  91. */
  92. detach() {
  93. /** @type {?} */
  94. let host = this._attachedHost;
  95. if (host == null) {
  96. throwNoPortalAttachedError();
  97. }
  98. else {
  99. this._attachedHost = null;
  100. host.detach();
  101. }
  102. }
  103. /**
  104. * Whether this portal is attached to a host.
  105. * @return {?}
  106. */
  107. get isAttached() {
  108. return this._attachedHost != null;
  109. }
  110. /**
  111. * Sets the PortalOutlet reference without performing `attach()`. This is used directly by
  112. * the PortalOutlet when it is performing an `attach()` or `detach()`.
  113. * @param {?} host
  114. * @return {?}
  115. */
  116. setAttachedHost(host) {
  117. this._attachedHost = host;
  118. }
  119. }
  120. /**
  121. * A `ComponentPortal` is a portal that instantiates some Component upon attachment.
  122. * @template T
  123. */
  124. class ComponentPortal extends Portal {
  125. /**
  126. * @param {?} component
  127. * @param {?=} viewContainerRef
  128. * @param {?=} injector
  129. * @param {?=} componentFactoryResolver
  130. */
  131. constructor(component, viewContainerRef, injector, componentFactoryResolver) {
  132. super();
  133. this.component = component;
  134. this.viewContainerRef = viewContainerRef;
  135. this.injector = injector;
  136. this.componentFactoryResolver = componentFactoryResolver;
  137. }
  138. }
  139. /**
  140. * A `TemplatePortal` is a portal that represents some embedded template (TemplateRef).
  141. * @template C
  142. */
  143. class TemplatePortal extends Portal {
  144. /**
  145. * @param {?} template
  146. * @param {?} viewContainerRef
  147. * @param {?=} context
  148. */
  149. constructor(template, viewContainerRef, context) {
  150. super();
  151. this.templateRef = template;
  152. this.viewContainerRef = viewContainerRef;
  153. this.context = context;
  154. }
  155. /**
  156. * @return {?}
  157. */
  158. get origin() {
  159. return this.templateRef.elementRef;
  160. }
  161. /**
  162. * Attach the portal to the provided `PortalOutlet`.
  163. * When a context is provided it will override the `context` property of the `TemplatePortal`
  164. * instance.
  165. * @param {?} host
  166. * @param {?=} context
  167. * @return {?}
  168. */
  169. attach(host, context = this.context) {
  170. this.context = context;
  171. return super.attach(host);
  172. }
  173. /**
  174. * @return {?}
  175. */
  176. detach() {
  177. this.context = undefined;
  178. return super.detach();
  179. }
  180. }
  181. /**
  182. * Partial implementation of PortalOutlet that handles attaching
  183. * ComponentPortal and TemplatePortal.
  184. * @abstract
  185. */
  186. class BasePortalOutlet {
  187. constructor() {
  188. /**
  189. * Whether this host has already been permanently disposed.
  190. */
  191. this._isDisposed = false;
  192. }
  193. /**
  194. * Whether this host has an attached portal.
  195. * @return {?}
  196. */
  197. hasAttached() {
  198. return !!this._attachedPortal;
  199. }
  200. /**
  201. * Attaches a portal.
  202. * @param {?} portal
  203. * @return {?}
  204. */
  205. attach(portal) {
  206. if (!portal) {
  207. throwNullPortalError();
  208. }
  209. if (this.hasAttached()) {
  210. throwPortalAlreadyAttachedError();
  211. }
  212. if (this._isDisposed) {
  213. throwPortalOutletAlreadyDisposedError();
  214. }
  215. if (portal instanceof ComponentPortal) {
  216. this._attachedPortal = portal;
  217. return this.attachComponentPortal(portal);
  218. }
  219. else if (portal instanceof TemplatePortal) {
  220. this._attachedPortal = portal;
  221. return this.attachTemplatePortal(portal);
  222. }
  223. throwUnknownPortalTypeError();
  224. }
  225. /**
  226. * Detaches a previously attached portal.
  227. * @return {?}
  228. */
  229. detach() {
  230. if (this._attachedPortal) {
  231. this._attachedPortal.setAttachedHost(null);
  232. this._attachedPortal = null;
  233. }
  234. this._invokeDisposeFn();
  235. }
  236. /**
  237. * Permanently dispose of this portal host.
  238. * @return {?}
  239. */
  240. dispose() {
  241. if (this.hasAttached()) {
  242. this.detach();
  243. }
  244. this._invokeDisposeFn();
  245. this._isDisposed = true;
  246. }
  247. /**
  248. * \@docs-private
  249. * @param {?} fn
  250. * @return {?}
  251. */
  252. setDisposeFn(fn) {
  253. this._disposeFn = fn;
  254. }
  255. /**
  256. * @private
  257. * @return {?}
  258. */
  259. _invokeDisposeFn() {
  260. if (this._disposeFn) {
  261. this._disposeFn();
  262. this._disposeFn = null;
  263. }
  264. }
  265. }
  266. /**
  267. * @deprecated Use `BasePortalOutlet` instead.
  268. * \@breaking-change 9.0.0
  269. * @abstract
  270. */
  271. class BasePortalHost extends BasePortalOutlet {
  272. }
  273. /**
  274. * @fileoverview added by tsickle
  275. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  276. */
  277. /**
  278. * A PortalOutlet for attaching portals to an arbitrary DOM element outside of the Angular
  279. * application context.
  280. */
  281. class DomPortalOutlet extends BasePortalOutlet {
  282. /**
  283. * @param {?} outletElement
  284. * @param {?} _componentFactoryResolver
  285. * @param {?} _appRef
  286. * @param {?} _defaultInjector
  287. */
  288. constructor(outletElement, _componentFactoryResolver, _appRef, _defaultInjector) {
  289. super();
  290. this.outletElement = outletElement;
  291. this._componentFactoryResolver = _componentFactoryResolver;
  292. this._appRef = _appRef;
  293. this._defaultInjector = _defaultInjector;
  294. }
  295. /**
  296. * Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver.
  297. * @template T
  298. * @param {?} portal Portal to be attached
  299. * @return {?} Reference to the created component.
  300. */
  301. attachComponentPortal(portal) {
  302. /** @type {?} */
  303. const resolver = portal.componentFactoryResolver || this._componentFactoryResolver;
  304. /** @type {?} */
  305. const componentFactory = resolver.resolveComponentFactory(portal.component);
  306. /** @type {?} */
  307. let componentRef;
  308. // If the portal specifies a ViewContainerRef, we will use that as the attachment point
  309. // for the component (in terms of Angular's component tree, not rendering).
  310. // When the ViewContainerRef is missing, we use the factory to create the component directly
  311. // and then manually attach the view to the application.
  312. if (portal.viewContainerRef) {
  313. componentRef = portal.viewContainerRef.createComponent(componentFactory, portal.viewContainerRef.length, portal.injector || portal.viewContainerRef.injector);
  314. this.setDisposeFn((/**
  315. * @return {?}
  316. */
  317. () => componentRef.destroy()));
  318. }
  319. else {
  320. componentRef = componentFactory.create(portal.injector || this._defaultInjector);
  321. this._appRef.attachView(componentRef.hostView);
  322. this.setDisposeFn((/**
  323. * @return {?}
  324. */
  325. () => {
  326. this._appRef.detachView(componentRef.hostView);
  327. componentRef.destroy();
  328. }));
  329. }
  330. // At this point the component has been instantiated, so we move it to the location in the DOM
  331. // where we want it to be rendered.
  332. this.outletElement.appendChild(this._getComponentRootNode(componentRef));
  333. return componentRef;
  334. }
  335. /**
  336. * Attaches a template portal to the DOM as an embedded view.
  337. * @template C
  338. * @param {?} portal Portal to be attached.
  339. * @return {?} Reference to the created embedded view.
  340. */
  341. attachTemplatePortal(portal) {
  342. /** @type {?} */
  343. let viewContainer = portal.viewContainerRef;
  344. /** @type {?} */
  345. let viewRef = viewContainer.createEmbeddedView(portal.templateRef, portal.context);
  346. viewRef.detectChanges();
  347. // The method `createEmbeddedView` will add the view as a child of the viewContainer.
  348. // But for the DomPortalOutlet the view can be added everywhere in the DOM
  349. // (e.g Overlay Container) To move the view to the specified host element. We just
  350. // re-append the existing root nodes.
  351. viewRef.rootNodes.forEach((/**
  352. * @param {?} rootNode
  353. * @return {?}
  354. */
  355. rootNode => this.outletElement.appendChild(rootNode)));
  356. this.setDisposeFn(((/**
  357. * @return {?}
  358. */
  359. () => {
  360. /** @type {?} */
  361. let index = viewContainer.indexOf(viewRef);
  362. if (index !== -1) {
  363. viewContainer.remove(index);
  364. }
  365. })));
  366. // TODO(jelbourn): Return locals from view.
  367. return viewRef;
  368. }
  369. /**
  370. * Clears out a portal from the DOM.
  371. * @return {?}
  372. */
  373. dispose() {
  374. super.dispose();
  375. if (this.outletElement.parentNode != null) {
  376. this.outletElement.parentNode.removeChild(this.outletElement);
  377. }
  378. }
  379. /**
  380. * Gets the root HTMLElement for an instantiated component.
  381. * @private
  382. * @param {?} componentRef
  383. * @return {?}
  384. */
  385. _getComponentRootNode(componentRef) {
  386. return (/** @type {?} */ (((/** @type {?} */ (componentRef.hostView))).rootNodes[0]));
  387. }
  388. }
  389. /**
  390. * @deprecated Use `DomPortalOutlet` instead.
  391. * \@breaking-change 9.0.0
  392. */
  393. class DomPortalHost extends DomPortalOutlet {
  394. }
  395. /**
  396. * @fileoverview added by tsickle
  397. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  398. */
  399. /**
  400. * Directive version of a `TemplatePortal`. Because the directive *is* a TemplatePortal,
  401. * the directive instance itself can be attached to a host, enabling declarative use of portals.
  402. */
  403. class CdkPortal extends TemplatePortal {
  404. /**
  405. * @param {?} templateRef
  406. * @param {?} viewContainerRef
  407. */
  408. constructor(templateRef, viewContainerRef) {
  409. super(templateRef, viewContainerRef);
  410. }
  411. }
  412. CdkPortal.decorators = [
  413. { type: Directive, args: [{
  414. selector: '[cdkPortal]',
  415. exportAs: 'cdkPortal',
  416. },] },
  417. ];
  418. /** @nocollapse */
  419. CdkPortal.ctorParameters = () => [
  420. { type: TemplateRef },
  421. { type: ViewContainerRef }
  422. ];
  423. /**
  424. * @deprecated Use `CdkPortal` instead.
  425. * \@breaking-change 9.0.0
  426. */
  427. class TemplatePortalDirective extends CdkPortal {
  428. }
  429. TemplatePortalDirective.decorators = [
  430. { type: Directive, args: [{
  431. selector: '[cdk-portal], [portal]',
  432. exportAs: 'cdkPortal',
  433. providers: [{
  434. provide: CdkPortal,
  435. useExisting: TemplatePortalDirective
  436. }]
  437. },] },
  438. ];
  439. /**
  440. * Directive version of a PortalOutlet. Because the directive *is* a PortalOutlet, portals can be
  441. * directly attached to it, enabling declarative use.
  442. *
  443. * Usage:
  444. * `<ng-template [cdkPortalOutlet]="greeting"></ng-template>`
  445. */
  446. class CdkPortalOutlet extends BasePortalOutlet {
  447. /**
  448. * @param {?} _componentFactoryResolver
  449. * @param {?} _viewContainerRef
  450. */
  451. constructor(_componentFactoryResolver, _viewContainerRef) {
  452. super();
  453. this._componentFactoryResolver = _componentFactoryResolver;
  454. this._viewContainerRef = _viewContainerRef;
  455. /**
  456. * Whether the portal component is initialized.
  457. */
  458. this._isInitialized = false;
  459. /**
  460. * Emits when a portal is attached to the outlet.
  461. */
  462. this.attached = new EventEmitter();
  463. }
  464. /**
  465. * Portal associated with the Portal outlet.
  466. * @return {?}
  467. */
  468. get portal() {
  469. return this._attachedPortal;
  470. }
  471. /**
  472. * @param {?} portal
  473. * @return {?}
  474. */
  475. set portal(portal) {
  476. // Ignore the cases where the `portal` is set to a falsy value before the lifecycle hooks have
  477. // run. This handles the cases where the user might do something like `<div cdkPortalOutlet>`
  478. // and attach a portal programmatically in the parent component. When Angular does the first CD
  479. // round, it will fire the setter with empty string, causing the user's content to be cleared.
  480. if (this.hasAttached() && !portal && !this._isInitialized) {
  481. return;
  482. }
  483. if (this.hasAttached()) {
  484. super.detach();
  485. }
  486. if (portal) {
  487. super.attach(portal);
  488. }
  489. this._attachedPortal = portal;
  490. }
  491. /**
  492. * Component or view reference that is attached to the portal.
  493. * @return {?}
  494. */
  495. get attachedRef() {
  496. return this._attachedRef;
  497. }
  498. /**
  499. * @return {?}
  500. */
  501. ngOnInit() {
  502. this._isInitialized = true;
  503. }
  504. /**
  505. * @return {?}
  506. */
  507. ngOnDestroy() {
  508. super.dispose();
  509. this._attachedPortal = null;
  510. this._attachedRef = null;
  511. }
  512. /**
  513. * Attach the given ComponentPortal to this PortalOutlet using the ComponentFactoryResolver.
  514. *
  515. * @template T
  516. * @param {?} portal Portal to be attached to the portal outlet.
  517. * @return {?} Reference to the created component.
  518. */
  519. attachComponentPortal(portal) {
  520. portal.setAttachedHost(this);
  521. // If the portal specifies an origin, use that as the logical location of the component
  522. // in the application tree. Otherwise use the location of this PortalOutlet.
  523. /** @type {?} */
  524. const viewContainerRef = portal.viewContainerRef != null ?
  525. portal.viewContainerRef :
  526. this._viewContainerRef;
  527. /** @type {?} */
  528. const resolver = portal.componentFactoryResolver || this._componentFactoryResolver;
  529. /** @type {?} */
  530. const componentFactory = resolver.resolveComponentFactory(portal.component);
  531. /** @type {?} */
  532. const ref = viewContainerRef.createComponent(componentFactory, viewContainerRef.length, portal.injector || viewContainerRef.injector);
  533. super.setDisposeFn((/**
  534. * @return {?}
  535. */
  536. () => ref.destroy()));
  537. this._attachedPortal = portal;
  538. this._attachedRef = ref;
  539. this.attached.emit(ref);
  540. return ref;
  541. }
  542. /**
  543. * Attach the given TemplatePortal to this PortlHost as an embedded View.
  544. * @template C
  545. * @param {?} portal Portal to be attached.
  546. * @return {?} Reference to the created embedded view.
  547. */
  548. attachTemplatePortal(portal) {
  549. portal.setAttachedHost(this);
  550. /** @type {?} */
  551. const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context);
  552. super.setDisposeFn((/**
  553. * @return {?}
  554. */
  555. () => this._viewContainerRef.clear()));
  556. this._attachedPortal = portal;
  557. this._attachedRef = viewRef;
  558. this.attached.emit(viewRef);
  559. return viewRef;
  560. }
  561. }
  562. CdkPortalOutlet.decorators = [
  563. { type: Directive, args: [{
  564. selector: '[cdkPortalOutlet]',
  565. exportAs: 'cdkPortalOutlet',
  566. inputs: ['portal: cdkPortalOutlet']
  567. },] },
  568. ];
  569. /** @nocollapse */
  570. CdkPortalOutlet.ctorParameters = () => [
  571. { type: ComponentFactoryResolver },
  572. { type: ViewContainerRef }
  573. ];
  574. CdkPortalOutlet.propDecorators = {
  575. attached: [{ type: Output }]
  576. };
  577. /**
  578. * @deprecated Use `CdkPortalOutlet` instead.
  579. * \@breaking-change 9.0.0
  580. */
  581. class PortalHostDirective extends CdkPortalOutlet {
  582. }
  583. PortalHostDirective.decorators = [
  584. { type: Directive, args: [{
  585. selector: '[cdkPortalHost], [portalHost]',
  586. exportAs: 'cdkPortalHost',
  587. inputs: ['portal: cdkPortalHost'],
  588. providers: [{
  589. provide: CdkPortalOutlet,
  590. useExisting: PortalHostDirective
  591. }]
  592. },] },
  593. ];
  594. class PortalModule {
  595. }
  596. PortalModule.decorators = [
  597. { type: NgModule, args: [{
  598. exports: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective],
  599. declarations: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective],
  600. },] },
  601. ];
  602. /**
  603. * @fileoverview added by tsickle
  604. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  605. */
  606. /**
  607. * Custom injector to be used when providing custom
  608. * injection tokens to components inside a portal.
  609. * \@docs-private
  610. */
  611. class PortalInjector {
  612. /**
  613. * @param {?} _parentInjector
  614. * @param {?} _customTokens
  615. */
  616. constructor(_parentInjector, _customTokens) {
  617. this._parentInjector = _parentInjector;
  618. this._customTokens = _customTokens;
  619. }
  620. /**
  621. * @param {?} token
  622. * @param {?=} notFoundValue
  623. * @return {?}
  624. */
  625. get(token, notFoundValue) {
  626. /** @type {?} */
  627. const value = this._customTokens.get(token);
  628. if (typeof value !== 'undefined') {
  629. return value;
  630. }
  631. return this._parentInjector.get(token, notFoundValue);
  632. }
  633. }
  634. /**
  635. * @fileoverview added by tsickle
  636. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  637. */
  638. /**
  639. * @fileoverview added by tsickle
  640. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  641. */
  642. export { Portal, ComponentPortal, TemplatePortal, BasePortalOutlet, BasePortalHost, DomPortalOutlet, DomPortalHost, CdkPortal, TemplatePortalDirective, CdkPortalOutlet, PortalHostDirective, PortalModule, PortalInjector };
  643. //# sourceMappingURL=portal.js.map