The Condent Conductor module provides the functionality for moving TemplateRefs between multiple ViewContainerRefs referenced with string name values.
Containers can be defined inside a Component template with the ContentContainerDirective or a custom directive that extends ContentContainerDirective.
<ng-container dvk-content-container="one"></ng-container>
<ng-container dvk-content-container="two"></ng-container>
The string names provided to the directive can be used to reference that container through an instance of ContentConductor.
Content is supplied through either the ContentDirective or a custom directive that extends it. This is a structural directive so when used it must be prefixed with a *
like *ngIf
or *ngFor
.
The ContentDirective takes an input which represents the string name of the ContentContainerDirective to initially attach the view to.
dynamicContents = [];
dContainerName = 'one';
addContentToAlternatingContainer() {
this.dynamicContents.push(0);
this.dContainerName = this.dContainerName === 'one'? 'two':'one';
}
<app-containers>
<div *dvk-content="'one'">Conductor Content 1</div>
<div *dvk-content="'two'">Conductor Content 2</div>
<ng-container *ngFor="let c of dynamicContents">
<div *dvk-content="dContainerName">New Content</div>
</ng-container>
</app-containers>
Structural directive inputs are bindings. So the value given to the directive must either be a bound variable, *dvk-content="containerName"
, or an anyonmous string variable, *dvk-content="'one'"
. Providing a bare string, *dvk-content="one"
will result in the template attempting to bind to the component controller member one
.
The ContentDirective can also be extended in order to provide preset initial container names or other functionality.
@Directive({
selector: '[one-content]'
})
export class OneContentDirective extends ContentDirective {
readonly initialContainerName = 'one';
constructor(private tRef: TemplateRef<any>) { super(tRef) }
}
@Directive({
selector: '[two-content]'
})
export class TwoContentDirective extends ContentDirective {
readonly initialContainerName = 'two';
constructor(private tRef: TemplateRef<any>) { super(tRef); }
}
oneContents = [];
twoContents = [];
addToOne() {
this.oneContents.push(0);
}
addToTwo() {
this.twoContents.push(0);
}
<app-containers>
<ng-container *ngFor="let c of oneContents">
<div *one-content>One Content</div>
</ng-container>
<ng-container *ngFor="let c of twoContents">
<div *two-content>Two Content</div>
</ng-container>
</app-containers>
The ContentConductor can be created by injecting the ContentConductorService into a component's constructor.
constructor(private ccService: ContentConductorService) { }
And then calling the createContentConductor method with the QueryLists for the ContentContainerDirectives and the ContentDirectives or their derivatives. Generally, these QueryLists should be available during a components' ngAfterViewInit lifecycle method.
The init method starts listening to the QueryLists for changes.
@ContentChildren(ContentDirective,{ descendants: true })
contents: QueryList<ContentDirective<any>>;
@ViewChildren(ContentContainerDirective)
containers: QueryList<ContentContainerDirective>;
conductor: ContentConductor<ContentContainer>;
ngAfterViewInit() {
this.conductor = this.ccService
.createContentConductor(this.containers, this.contents);
this.conductor.init('one');
}
Or provide multiple ContentDirectives or ContentContainerDirectives.
@ContentChildren(ContentDirective,{ descendants: true })
contents: QueryList<ContentDirective>;
@ContentChildren(OneContentDirective,{ descendants: true })
oneContents: QueryList<OneContentDirective>;
@ContentChildren(TwoContentDirective,{ descendants: true })
twoContents: QueryList<TwoContentDirective>;
@ViewChildren(ContentContainerDirective)
containers: QueryList<ContentContainerDirective>;
conductor: ContentConductor<ContentContainer>;
constructor(
private ccService: ContentConductorService) { }
ngAfterViewInit() {
this.conductor = this.ccService
.createContentConductor(
[this.containers],
[this.contents,this.oneContents,this.twoContents]);
this.conductor.init();
}
Then content can be moved from one container to another by specifying the container string names.
this.conductor.moveViews('one','two');
The ContentConductor also provides the ability to move single views from one container to another based on their index inside the container.
this.conductor.moveView('one','two',3);
Detach a single view from any container returning the ViewRef.
const vRef: ViewRef = this.conductor.detachView('one',2);
Attach a view to the end of a specified container.
this.conductor.attachView('two',vRef);
Detach all views from a specified container.
const views: ViewRef[] = this.conductor.detachViews('one');
And attach an array of views to a specified container.
this.conductor.attachViews('two',views);
Finally the content conductor should be destroyed inside the components ngOnDestroy method in order to free up the resources it uses.
ngOnDestroy() {
this.conductor.destroy();
}
All of the methods that insert views into containers also have an optional index parameter at the end that is the index to insert at. If it is not specified the items are inserted to the end of the containers views.