@memberjunction/ng-container-directives
Version:
MemberJunction: Angular Container Directives - Fill Container for Auto-Resizing, and plain container just for element identification/binding
287 lines (211 loc) • 9.04 kB
Markdown
# @memberjunction/ng-container-directives
Angular directives for container management in MemberJunction applications, providing flexible and responsive layout utilities.
## Overview
This package provides two essential directives for managing container layouts in Angular applications:
- **mjContainer**: Exposes a ViewContainerRef for dynamic component loading
- **mjFillContainer**: Automatically resizes elements to fill their parent containers with intelligent context awareness
## Features
- `mjContainer` directive for view container management and dynamic component loading
- `mjFillContainer` directive for responsive filling of parent containers
- Automatic resizing on window resize events with dual debounce strategy
- Manual resize event handling via MJGlobal event system
- Customizable margin and dimension settings
- Smart context detection (automatically skips resizing in grids, hidden tabs, and elements with `mjSkipResize`)
- Efficient resize event handling with configurable debouncing
- Debug mode for troubleshooting resize behavior
## Installation
```bash
npm install @memberjunction/ng-container-directives
```
## Usage
Import the module in your application:
```typescript
import { ContainerDirectivesModule } from '@memberjunction/ng-container-directives';
@NgModule({
imports: [
// ...
ContainerDirectivesModule
]
})
export class YourModule { }
```
### mjContainer
The `mjContainer` directive exposes a ViewContainerRef for dynamic component loading. This is particularly useful when you need to programmatically create and insert components into the DOM.
```html
<div mjContainer></div>
```
In your component:
```typescript
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { Container } from '@memberjunction/ng-container-directives';
@Component({
selector: 'app-your-component',
template: `<div mjContainer></div>`
})
export class YourComponent {
@ViewChild(Container, { static: true }) container!: Container;
ngOnInit() {
// Access the ViewContainerRef for dynamic component creation
const viewContainerRef: ViewContainerRef = this.container.viewContainerRef;
// Example: Create a dynamic component
// const componentRef = viewContainerRef.createComponent(YourDynamicComponent);
}
}
```
### mjFillContainer
Use the `mjFillContainer` directive to make an element fill its parent container:
```html
<!-- Basic usage (fills both width and height) -->
<div mjFillContainer>Content</div>
<!-- With custom settings -->
<div
mjFillContainer
[fillWidth]="true"
[fillHeight]="true"
[rightMargin]="10"
[bottomMargin]="20">
Content with margins
</div>
<!-- Fill only width -->
<div
mjFillContainer
[fillWidth]="true"
[fillHeight]="false">
Content that fills width only
</div>
```
### Skip Resize
If you need to prevent the resize behavior for certain elements:
```html
<!-- This element will not be resized by the directive -->
<div mjSkipResize>Content</div>
```
### Manual Resize Triggering
You can trigger manual resizing using the MemberJunction global events:
```typescript
import { MJGlobal, MJEventType } from '@memberjunction/global';
// Trigger resize
MJGlobal.Instance.RaiseEvent({
event: MJEventType.ManualResizeRequest,
args: null
});
```
## Configuration
The `mjFillContainer` directive has several configuration options:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| fillWidth | boolean | true | Whether to fill the parent's width |
| fillHeight | boolean | true | Whether to fill the parent's height |
| rightMargin | number | 0 | Right margin in pixels |
| bottomMargin | number | 0 | Bottom margin in pixels |
## How It Works
The `mjFillContainer` directive dynamically calculates and sets element dimensions based on its parent container:
1. **Parent container detection**: The directive identifies the nearest block-level parent element.
2. **Size calculation**:
- When `fillWidth` is true, it calculates the element's width based on its parent's width, accounting for the element's position within the parent and any `rightMargin`.
- When `fillHeight` is true, it calculates height similarly, accounting for the `bottomMargin`.
3. **Event handling**: The directive listens for:
- Window resize events (with two debounce times: 100ms during active resizing, 500ms after resizing completes)
- Custom MJ application resize events via the MJGlobal event system
4. **Context-aware behavior**: The directive automatically skips resizing under certain conditions:
- Elements with the `mjSkipResize` attribute (or any parent with this attribute)
- Elements within a grid (role="grid")
- Elements within hidden tabs (not currently active)
- Elements with hidden or not displayed parents
## Common Use Cases
### When to Use `[fillHeight]="true" [fillWidth]="false"`
- Vertical scrollable areas where you want fixed width but dynamic height
- Content panels that should stretch to fill available vertical space
- Example: Sidebar navigation that fills vertical space but has fixed width
### When to Use `[fillHeight]="false" [fillWidth]="true"`
- Horizontal elements like headers or toolbars that span full width
- Fixed-height components that need to adapt to different screen widths
- Example: Form controls that adjust width but maintain consistent height
### When to Use Both (Default)
- Main content areas that should fill the entire available space
- Split panels or layouts that need to adapt to window resizing
- Example: Dashboards, content editors, or any primary workspace area
## Performance Optimization
- **Minimize unnecessary instances**: Only apply to containers that truly need dynamic sizing
- **Use `mjSkipResize` appropriately**: Apply to elements that don't need resizing
- **Consider debouncing**: The directive already implements debouncing, but be aware of performance impact with many instances
## Nested Containers
When nesting components with `mjFillContainer`:
1. The parent container should have the directive applied with appropriate settings
2. Child elements inherit the size constraints of their parents
3. Adjustments are calculated top-down, so parent resizing triggers child resizing
4. Example:
```html
<div mjFillContainer [fillHeight]="true" class="main-container">
<div class="header" style="height: 60px;">Header</div>
<div mjFillContainer [fillHeight]="true" class="content-area">
<!-- This will fill the remaining height after the header -->
Content
</div>
</div>
```
## Dependencies
This package depends on:
- `@memberjunction/core` - Core MemberJunction utilities and logging
- `@memberjunction/global` - Global event system for manual resize triggers
- `rxjs` - For event handling and debouncing
Peer dependencies:
- `@angular/common` ^18.0.2
- `@angular/core` ^18.0.2
- `@angular/router` ^18.0.2
## Troubleshooting
### Element not resizing properly
- Check if any parent has `mjSkipResize` attribute
- Verify the element isn't within a grid (role="grid") or hidden tab
- Ensure parent elements have proper CSS display properties (must be 'block')
- Check z-index and overflow settings
- Verify parent visibility (elements with hidden or not displayed parents are skipped)
### Flickering during resize
- This is usually caused by cascading resize calculations
- Try applying `mjFillContainer` only where necessary
- Use CSS transitions for smoother visual changes
- Consider the dual debounce strategy (100ms during resize, 500ms after)
### Height calculation issues
- Ensure parent element has a defined height or position
- For full window height, apply directive to a root element
- Check for competing CSS that might override the directive's styles
- Note that padding is accounted for in calculations
## Advanced Controls
For debugging or special cases, there are static properties on the FillContainer class:
```typescript
import { FillContainer } from '@memberjunction/ng-container-directives';
// Disable resize globally (for all instances)
FillContainer.DisableResize = true;
// Enable resize debugging (logs to console)
FillContainer.OutputDebugInfo = true;
```
## API Reference
### Container Directive
```typescript
@Directive({
selector: '[mjContainer]'
})
export class Container {
constructor(public viewContainerRef: ViewContainerRef) { }
}
```
### FillContainer Directive
```typescript
@Directive({
selector: '[mjFillContainer]'
})
export class FillContainer {
@Input() fillWidth: boolean = true;
@Input() fillHeight: boolean = true;
@Input() rightMargin: number = 0;
@Input() bottomMargin: number = 0;
static DisableResize: boolean = false;
static OutputDebugInfo: boolean = false;
}
```
## Contributing
When contributing to this package:
1. Follow the MemberJunction development guidelines
2. Ensure all TypeScript compiles without errors: `npm run build`
3. Update this README if adding new features or changing behavior
4. Add appropriate TSDoc comments to all public APIs