How to apply a class to ng-container outlet in Angular2+? - javascript

How can I apply the class "exampleClass" to the content of the container?
class in custom.component.scss:
exampleClass {
backgroundColor: "red";
}
HTML in Component of the custom.component.html:
<ng-container
[ngTemplateOutlet]="customDirective.templateRef"
[ngTemplateOutletContext]="{ $implicit: item, index: i}">
</ng-container>
the HTML where i use it somewhere.component.html:
<custom-component>
<ng-template customDirective>
<div>
the div i want to apply the class
// if i apply the class directly here it works, but i dont like this, cause this class is
// not part of this component
<div>
</ng-template>
</custom component>
i also tried to wrap it and change the class - but it wont work:
<div class="exampleClass">
<ng-container
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{ $implicit: item, index: i}">
</ng-container>
</div>
modified class:
exampleClass > div {
backgroundColor: "yellow";
}

Related

I don't know how to create a changeable text for a reusable component in Vue Js

I was creating a reusable tabs component by watching tutorial. Having watched it, I figured out how it works. But, as for my project, I need to create tabs with heading containing a title that can be changed because this is a reusable component, so I have to change each heading's title for each new tab, but I haven't figured out how to do it. I need somehow to get title from component TabsWrapper that I added to my page
<div class="tab_header">{{title}}</div>
and then make this title change a text inside this div that is the main header of a TabsWrapper component.
<div class="tab_header">{{title}}</div>
My code:
The first one is the code out of component that I added to my homepage of the website.
<TabsWrapper>
<Tab title="Tab 1">Hello 1</Tab>
<Tab title="Tab 2">Hello 2 </Tab>
<Tab title="Tab 3">Hello 3</Tab>
<Tab title="Tab 4">Hello 4</Tab>
</TabsWrapper>
The second one is the code inside the component that is responsible for TabsWrapper
<template>
<div class="tabs">
<div class="tab_header"></div>
<ul class="tabs_header">
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
</ul>
<slot />
</div>
</template>
<script>
import { ref} from 'vue';
import { provide } from 'vue';
export default{
setup(props,{slots}){
const tabTitles=ref(slots.default().map((tab)=> tab.props.title))
const selectedTitle=ref(tabTitles.value[0])
provide("selectedTitle", selectedTitle)
return{
selectedTitle,
tabTitles,
}
}
}
</script>
This chunk of code takes each title from Tab
<Tab title="Tab 1">Hello 1</Tab>
And this chunk of code renders it out
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
I've tried to repeat the same technique, but it worked out, but I think that there's the better way to do it
<div class="tabs">
<div class="tab_header" v-for="headtitle in headTitles" :key="headtitle">{{headtitle}}</div>
<ul class="tabs_header">
<li
v-for="title in tabTitles"
:key="title"
#click="selectedTitle = title"
:class=" {selected: title ==selectedTitle}"
>
{{ title }}
</li>
</ul>
<slot />
</div>
</template>
<script>
import { ref} from 'vue';
import { provide } from 'vue';
export default{
setup(props,{slots}){
const tabTitles=ref(slots.default().map((tab)=> tab.props.title))
const headTitles=ref(slots.default().map((tab)=>tab.props.headtitle))
const selectedTitle=ref(tabTitles.value[0])
provide("selectedTitle", selectedTitle)
return{
selectedTitle,
tabTitles,
headTitles,
}
}
}
</script>
You can simply pass props in the script tag and directly access them using this keyword and prop name.
export default {
props: ['foo'],
created() {
// props are exposed on `this`
console.log(this.foo)
}
}
In the template tag like this
<span>{{ foo }}</span>
you don't need to use ref you can directly use v-for and loop over the array elements.
<li v-for="(item, index) in foo">
{{item}}
</li>

Vue.js - pass template to grandchild

I have a component that defines a set of slots dynamically from props like this:
<template v-for="(field, index) in fields">
<div
v-if="!field.hide"
:key="index"
class="my-class"
>
<slot :name="getSlotName(field.name, 'label')" />
</div>
</template>
And I have a component WrapperComponent that uses template to fill those slots:
<slotted-component :fields="[{name: 'f1'}]">
<template #f1>
Some content here
</template>
</slotted-component>
And I want to be able to override the content of <template #f1> from the parent component of the WrapperComponent like
<WrapperComponent>
<template #f1>
Some other content
</template>
</WrapperComponent>
I tried to use a PassThroughTemplate component with this template:
<template>
<template v-slot:[name]>
<slot
:name="name"
v-bind="$attrs"
/>
</template>
</template>
but it gives me a compilation error if not compiling at runtime (only eslint yells in this case). I want to use render function, but this code doesn't render anything:
render (h, ctx) {
return h(
'template',
{slot: ctx.props.name},
h('slot', {name: ctx.props.name})
)
}
and i don't know how to use it properly. So, is there a way to do that or another way to achieve template replacement?

Expand ngx-datatable on view load

Have the below ngx-datatable where I want to have the first row expanded on view load. I have tried to call the toggleExpandRow(this.table.row[0]) on the angular lifecycle hook ngAfterViewChecked and the template is not getting updated with the row expanding. Has anyone accomplished this before with ngx-datatable? The below code does not run and is there for information purposes only.
import {
Component,
Input,
Output,
EventEmitter,
OnInit,
ChangeDetectionStrategy,
ViewEncapsulation,
ViewChild
} from '#angular/core';
import { defaultTrackByFn } from '#shared/utils';
import { Datatable } from '#app/stores/dashboard-store/models/datatable.model';
import { DatatableComponent as ngxDatatableComponent } from '#swimlane/ngx-datatable';
import { Observable } from 'rxjs';
import { ActiveToggleService } from '#dashboard/services/ActiveToggle.service';
#Component({
selector: 'fmsl-datatable',
templateUrl: './datatable.component.html',
styleUrls: ['./datatable.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DatatableComponent implements OnInit {
#Input()
datatable: Datatable;
#Input()
isBorrower: boolean;
#ViewChild('loanTable')
table: ngxDatatableComponent;
#Input()
showLegend: boolean = true;
trackByFn: Function = defaultTrackByFn;
// TODO: Implement Datatable Actions
actions = ['replace with logic later'];
isBorrower$: Observable<boolean>;
constructor(private _toggleService: ActiveToggleService) {}
ngOnInit() {
// console.log('table:', this.table)
// console.log('table row detail', this.table.rowDetail)
// this.table.rowDetail.expandAllRows();
}
ngAfterViewChecked() {
console.log('table:', this.table)
console.log('table row detail', this.table.rowDetail)
console.log('table row:', this.table._rows[0])
this.table.rowDetail.toggleExpandRow(this.table._rows[0]);
}
toggleExpandRow(row) {
console.log('row toggled', row);
console.log('row table', this.table);
this.table.rowDetail.toggleExpandRow(row);
}
onDetailToggle(event) {
// on row detail expand we can do something
}
toggleActiveInactive(row) {
const { uuid, loanStatusTypeName } = row;
this._toggleService.toggleActiveInactive({ uuid, loanStatusTypeName });
}
}
<div class="row datatable-row">
<div class="col">
<!--mdc-replace datatable-->
<div [ngClass]="datatable.tableClass">
<!-- {{ datatable | json }} -->
<ngx-datatable
#loanTable
class="material expandable"
[rows]="datatable.rows"
[columnMode]="'flex'"
rowHeight="auto"
sortType="single"
[headerHeight]="42"
[footerHeight]="0"
[scrollbarH]="true"
[footerHeight]="0"
[loadingIndicator]="datatable.loading"
[messages]="datatable.messages"
>
<ngx-datatable-row-detail [rowHeight]="44" (toggle)="onDetailToggle($event)">
<ng-template
let-row="row"
let-expanded="expanded"
ngx-datatable-row-detail-template
>
<div class="expansion-row" style="padding-left:35px;">
<mdc-icon class="material-icons" aria-hidden="true">info_outline</mdc-icon>
<span
[innerHTML]="row.statusProperties.statusDetail"
class="datatable__expansion-row-message"
></span>
</div>
</ng-template>
</ngx-datatable-row-detail>
<ngx-datatable-column
[width]="50"
[resizeable]="false"
[sortable]="false"
[draggable]="false"
[canAutoResize]="false"
>
<ng-template let-row="row" let-expanded="expanded" ngx-datatable-cell-template>
<a
href="javascript:void(0)"
[class.datatable-icon-right]="!expanded"
[class.datatable-icon-down]="expanded"
title="Expand/Collapse Row"
(click)="toggleExpandRow(row)"
>
</a>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column
*ngFor="let col of datatable.columns; index as i; trackBy: trackByFn"
[name]="col.name"
resizeable="false"
[flexGrow]="col.flex"
[prop]="col.prop"
[pipe]="col.pipe"
>
<ng-template let-column="column" ngx-datatable-header-template let-expanded="true">
{{ col.name }}
</ng-template>
<ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
<div class="table-cell justify-content-start" [attr.data-heading]="col.name">
<span *ngIf="col.isIcon; else progressCell">
<fmsl-datatable-icon
*ngIf="value"
[actionRequiredBy]="value"
[legend]="datatable.legend"
[currentRole]="datatable.currentRole"
></fmsl-datatable-icon>
</span>
<div *ngIf="col.isActions" class="rowActions">
<button mdc-button (click)="toggleActiveInactive(row)">
Mark {{ row.loanStatusTypeName === 'active' ? 'Inactive' : 'Active' }}
</button>
</div>
<ng-template #progressCell>
<div *ngIf="col.isProgress; else linkCell" class="progress-container">
<mdc-linear-progress
class="progress-bar loan-status-progress-bar"
[determinate]="true"
[progress]="value"
></mdc-linear-progress>
</div>
</ng-template>
<ng-template #linkCell>
<span *ngIf="col.isLink; else valueCell">
<a
*ngIf="row.sharePointURL; else internalLink"
target="_blank"
href="{{ row.sharePointURL }}"
>{{ value }}
</a>
<ng-template #internalLink>
<a [routerLink]="['/dashboard/deal', 'loan-request-info', row.uuid]">{{
value
}}</a>
</ng-template>
</span>
</ng-template>
<ng-template #valueCell>
<span>{{ value }}</span>
</ng-template>
</div>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>
</div>
</div>
There is an open issue https://github.com/swimlane/ngx-datatable/issues/929 while using toggle functions with onInit and afterViewInit hooks.
To call toggle function inside ngAfterViewChecked is not a good idea, as this hook will be called multiple times. And once you get it expanded, you cannot collapse it.
Still there is a dirty workaround to call the toggle function inside AfterViewInit within setTimeout:
#ViewChild(DatatableComponent) private table: DatatableComponent;
constructor(private cdRef: ChangeDetectorRef) {}
setTimeout(() => {
this.table.rowDetails.toggleExpandedRow(ROW);
this.cdRef.detectChanges();
}, 1000);

Rendering <ng-content> in angular 2 more times

I have code like this
<template *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<ng-content></ng-content>
</div>
</div>
</template>
<template *ngIf='desktop'>
<ng-content></ng-content>
</template>
However, Angular 2 renders ng-content just one time. Is there a way to get this case working properly without too much hacking?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
You can pass the content as a template, then you can render it multiple times.
<parent>
<template>
projected content here
</template>
</parent>
In parent
<ng-container *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<template [ngTemplateOutlet]="templateRef"></template>
</div>
</div>
</ng-container>
<template *ngIf='desktop'>
<template [ngTemplateOutlet]="templateRef"></template>
</template>
export class ParentComponent {
constructor(private templateRef:TemplateRef){}
}
You can also pass data to the template to bind with the projected content.
<ng-container *ngIf='mobile'>
<div class='wrapper'>
<div class='nav'>
<template [ngTemplateOutlet]="templateRef" [ntOutletContext]="{ $implicit: data}"></template>
</div>
</div>
</ng-container>
<ng-container *ngIf='desktop'>
<template [ngTemplateOutlet]="templateRef" [ntOutletContext]="{ $implicit: data}"></template>
</ng-container>
export class ParentComponent {
#Input() data;
constructor(private templateRef:TemplateRef){}
}
and then use it like
<parent [data]="data">
<template let-item>
projected content here {{item}}
</template>
</parent>
See also My own Angular 2 Table component - 2 way data binding

angular2 Can't have multiple template bindings on one element

I have this angular2 template:
<template *ngIf="alerts.length > 0">
<alert *ngFor="let alert of alerts;let i = index" [type]="alert.type" dismissible="true" (close)="closeAlert(i)">
{{ alert?.msg }}
</alert>
</template>
I get these errors:
zone.js:461 Unhandled Promise rejection: Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * (" </div>
<div *ngSwitchCase="false" class="container p-t-10">
<alert *ngIf="alerts.length > 0" [ERROR ->]*ngFor="let alert of alerts;let i = index" [type]="alert.type" dismissible="true" (close)="closeAlert"): b#5:37
what's the problem I put *ngIf and *ngFor in defferent html elements. It should work. no?
and:
Can't bind to 'type' since it isn't a known property of 'alert'. (""container p-t-10">
<alert *ngIf="alerts.length > 0" *ngFor="let alert of alerts;let i = index" [ERROR ->][type]="alert.type" dismissible="true" (close)="closeAlert(i)">
{{ alert?.msg }}
</alert>
"): b#5:80
I added the
*ngIf="alerts.length > 0 to avoid cases of alert = []. How can i fix it otherwise?
The * in *ngFor makes Angular to add a <template> tag. On a <template> tag this doesn't make sense and therefore here structural directives have a different syntax.
<template ngFor [ngForOf]="alerts" let-alert let-i="index">
Because different syntax for almost the same on different places caused quite some confusion, the Angular team recently introduced
<ng-container>
that behaves similar to the <template> tag (is not added to the DOM) but allows the more common syntax
<ng-container *ngIf="alerts.length > 0">
<alert *ngFor="let alert of alerts;let i = index" [type]="alert.type" dismissible="true" (close)="closeAlert(i)">
{{ alert?.msg }}
</alert>
</ng-container>
You should use <ng-container> for this case. As a example:
<ng-container *ngIf="totalItems > 0">
<tr *ngFor="let item of outputs| paginate: { id:'ab', itemsPerPage: pageSize, currentPage: currentPage, totalItems: totalItems }; let $index = index">
<td>{{item.x}}</td>
<td>{{item.y | date: 'MMMM d, y, h:mm:ss a' }}</td>
<td>{{item.z}}</td>
<td>{{item.r}}</td>
</tr>
</ng-container>
<ng-container *ngIf="totalItems > 10">
<tr *ngFor="let item of outputs| paginate: { id:'aabbbbbbbbb', itemsPerPage: pageSize, currentPage: currentPage, totalItems: totalItems }; let $index = index">
<td>{{item.x}}</td>
<td>{{item.y | date: 'MMMM d, y, h:mm:ss a' }}</td>
<td>{{item.z}}</td>
<td>{{item.r}}</td>
</tr>
</ng-container>

Categories

Resources