Security News
38% of CISOs Fear They’re Not Moving Fast Enough on AI
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
ng-hub-ui-portal
Advanced tools
A flexible Angular portal library for dynamic content rendering with advanced positioning and interaction control
A lightweight and flexible Angular portal library for dynamically rendering components and templates in any DOM container. Part of the ng-hub-ui suite of Angular components, this library provides a streamlined way to manage dynamic content rendering with full control over positioning and interaction.
ng-hub-ui-portal allows you to create dynamic portal windows that can render components, templates, or simple text content. Unlike traditional modal dialogs, portals can be rendered in any DOM container, making them more versatile for complex UI requirements.
npm install ng-hub-ui-portal
Start by importing the HubPortal
service in your component:
import { HubPortal } from 'ng-hub-ui-portal';
@Component({...})
export class YourComponent {
constructor(private portal: HubPortal) {}
openPortal() {
this.portal.open(YourContentComponent);
}
}
Create a component to be displayed in the portal:
@Component({
template: `
<div class="portal-header">
<h4>Portal Title</h4>
<button type="button" data-dismiss="portal">Close</button>
</div>
<div class="portal-body">
Your content here
</div>
<div class="portal-footer">
<button data-close="portal">Close</button>
</div>
`
})
export class PortalContentComponent {
constructor(private activePortal: HubActivePortal) {}
// Method to close the portal with a result
close() {
this.activePortal.close('Closed');
}
// Method to dismiss the portal with a reason
dismiss() {
this.activePortal.dismiss('Dismissed');
}
}
You can also use template references:
@Component({
template: `
<ng-template #content>
<div class="portal-header">
<h4>Template Portal</h4>
</div>
<div class="portal-body">
Template content here
</div>
</ng-template>
<button (click)="openPortal(content)">Open Portal</button>
`
})
export class YourComponent {
constructor(private portal: HubPortal) {}
openPortal(content: TemplateRef<any>) {
this.portal.open(content);
}
}
Note: When opening a portal, consider specifying a container where it will be rendered. While it defaults to body
, it's recommended to explicitly define your container for better control and organization.
The portal's container
option determines where in the DOM the portal will be rendered. While it defaults to the document body, specifying a custom container gives you better control over portal placement and management.
You can specify a container using either a CSS selector or a direct HTMLElement reference:
// Using a CSS selector
const portalRef = this.portal.open(YourComponent, {
container: '#myContainer'
});
// Or using toggle
const portalRef = this.portal.toggle(YourComponent, {
container: '#portalHost'
});
// Using an HTMLElement reference
const containerElement = document.querySelector('.portal-container');
const portalRef = this.portal.open(YourComponent, {
container: containerElement
});
// Recommended
const portalRef = this.portal.toggle(YourComponent, {
container: '#specificContainer'
});
// Less ideal - relies on default body container
const portalRef = this.portal.toggle(YourComponent);
<!-- In your template -->
<div id="portalContainer" class="portal-host">
<!-- Portals will be rendered here -->
</div>
.portal-host {
position: relative;
min-height: 100px;
}
The library provides two distinct methods for opening portals, each with its own specific behavior:
The open()
method uses a progressive rendering strategy. When you call open()
, the new portal is rendered while keeping any existing portals visible. This creates a layered effect where portals stack on top of each other, making it useful for scenarios where you want to display multiple related pieces of information simultaneously.
// First portal opens
const portal1 = this.portal.open(Component1);
// Second portal opens on top of the first one
const portal2 = this.portal.open(Component2);
// Third portal opens on top of both previous portals
const portal3 = this.portal.open(Component3);
This approach is particularly useful when:
The toggle()
method implements an exclusive rendering strategy. When called, it first ensures all existing portals are properly closed before rendering the new one. This creates a cleaner, single-portal experience:
// First portal opens
this.portal.toggle(Component1);
// First portal closes, then Component2 opens
this.portal.toggle(Component2);
// Second portal closes, then Component3 opens
this.portal.toggle(Component3);
This method is ideal when:
Consider these factors when deciding which method to use:
open()
when information from multiple portals needs to be visible simultaneouslytoggle()
when you want to ensure a clean transition between different portal contentstoggle()
ensures cleanup of previous portalsThe portal library provides a straightforward way to pass data to rendered components through the componentInstance
property of the portal reference. This allows you to directly interact with the component instance after it's been rendered.
Here's how to work with component data:
// Your portal component
@Component({
template: `
<div class="portal-header">
<h4>User Details</h4>
</div>
<div class="portal-body">
<p>Name: {{userName}}</p>
<p>Role: {{userRole}}</p>
</div>
`
})
export class UserDetailsComponent {
userName: string;
userRole: string;
updateUser(role: string) {
this.userRole = role;
}
}
// In your parent component
@Component({
template: `
<button (click)="openUserPortal()">Show User Details</button>
<button (click)="updateUserRole()">Promote User</button>
`
})
export class ParentComponent {
private portalRef: HubPortalRef;
constructor(private portal: HubPortal) {}
openUserPortal() {
// First, open the portal and store the reference
this.portalRef = this.portal.open(UserDetailsComponent);
// Then, access the component instance and set its properties
if (this.portalRef.componentInstance) {
this.portalRef.componentInstance.userName = 'John Doe';
this.portalRef.componentInstance.userRole = 'User';
}
}
updateUserRole() {
// You can call component methods through componentInstance
if (this.portalRef.componentInstance) {
this.portalRef.componentInstance.updateUser('Admin');
}
}
}
This approach gives you complete access to the component instance, allowing you to:
The same pattern works with the toggle
method:
const portalRef = this.portal.toggle(UserDetailsComponent);
portalRef.componentInstance.userName = 'John Doe';
portalRef.componentInstance.userRole = 'User';
For better TypeScript support, you can type your portal reference:
// Store the portal reference with the correct component type
private portalRef: HubPortalRef & { componentInstance: UserDetailsComponent };
openUserPortal() {
this.portalRef = this.portal.open(UserDetailsComponent);
// Now TypeScript knows all the available properties and methods
this.portalRef.componentInstance.userName = 'John Doe';
this.portalRef.componentInstance.updateUser('Admin');
}
The portal can be configured with various options:
this.portal.open(YourComponent, {
animation: true, // Enable/disable animations
container: 'body', // CSS selector or HTMLElement for portal container
scrollable: true, // Enable scrollable content
windowClass: 'custom-portal', // Additional CSS class for portal window
portalDialogClass: 'custom-dialog', // Additional CSS class for portal dialog
portalContentClass: 'custom-content', // Additional CSS class for portal content
headerSelector: '.portal-header', // Custom header selector
footerSelector: '.portal-footer', // Custom footer selector
dismissSelector: '[data-dismiss]', // Custom dismiss trigger selector
closeSelector: '[data-close]', // Custom close trigger selector
beforeDismiss: () => boolean, // Callback before portal dismissal
ariaLabelledBy: 'title-id', // ARIA labelledby attribute
ariaDescribedBy: 'desc-id', // ARIA describedby attribute
});
The open()
and toggle()
methods return a HubPortalRef
that you can use to control the portal:
const portalRef = this.portal.open(YourComponent);
// Close the portal with a result
portalRef.close('result');
// Dismiss the portal with a reason
portalRef.dismiss('reason');
// Update portal options
portalRef.update({
ariaLabelledBy: 'new-title-id',
portalContentClass: 'updated-content'
});
// Subscribe to portal events
portalRef.closed.subscribe(result => console.log('Portal closed:', result));
portalRef.dismissed.subscribe(reason => console.log('Portal dismissed:', reason));
portalRef.hidden.subscribe(() => console.log('Portal hidden'));
portalRef.shown.subscribe(() => console.log('Portal shown'));
You can provide default options for all portals:
@NgModule({
providers: [
{
provide: HubPortalConfig,
useValue: {
animation: true,
keyboard: true,
scrollable: false,
dismissSelector: '[data-dismiss="portal"]',
closeSelector: '[data-close="portal"]'
}
}
]
})
export class AppModule { }
The library includes methods to manage multiple portals:
// Check for open portals
if (this.portal.hasOpenPortals()) {
// Handle open portals
}
// Toggle portal (closes existing portals before opening new one)
this.portal.toggle(NewComponent);
// Dismiss all open portals
this.portal.dismissAll('reason');
// Subscribe to active portal instances
this.portal.activeInstances.subscribe(portals => {
console.log('Active portals:', portals.length);
});
The library implements accessibility features:
git clone https://github.com/carlos-morcillo/ng-hub-ui-portal.git
cd ng-hub-ui-portal
npm install
npm start
Run the test suite:
# Unit tests
npm run test
# E2E tests
npm run e2e
# Test coverage
npm run test:coverage
We follow Conventional Commits:
feat:
New featuresfix:
Bug fixesdocs:
Documentation changesstyle:
Code style changes (formatting, etc)refactor:
Code refactorstest:
Adding or updating testschore:
Maintenance tasksExample:
git commit -m "feat: add custom divider support"
git checkout -b feat/my-new-feature
Before creating an issue, please:
We follow the Angular Style Guide:
If you find this project helpful and would like to support its development, you can buy me a coffee:
Your support is greatly appreciated and helps maintain and improve this project!
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by [Carlos Morcillo Fernández]
FAQs
A flexible Angular portal library for dynamic content rendering with advanced positioning and interaction control
The npm package ng-hub-ui-portal receives a total of 33 weekly downloads. As such, ng-hub-ui-portal popularity was classified as not popular.
We found that ng-hub-ui-portal demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
Research
Security News
Socket researchers uncovered a backdoored typosquat of BoltDB in the Go ecosystem, exploiting Go Module Proxy caching to persist undetected for years.
Security News
Company News
Socket is joining TC54 to help develop standards for software supply chain security, contributing to the evolution of SBOMs, CycloneDX, and Package URL specifications.