I have developed a custom tab component using Angular 2 which is working fine.
This is the usage of it.
<us-tab-verticle>
<vtab-content [tabTitle]="'Basic Information'"><basic-info> </basic-info></vtab-content>
<vtab-content [tabTitle]="'VAT Settings'"><vat-settings> </vat-settings></vtab-content>
<vtab-content [tabTitle]= "'Economy Settings'"><economy-settings> </economy-settings></vtab-content>
<vtab-content [tabTitle]="'Access Profiles'"><access-profiles> </access-profiles></vtab-content>
</us-tab-verticle>
The problem is all the tab components are loading when the view load.
My tab implementation is as follows.
us-tab-verticle.component.html
<div class="v-tabs">
<div class="v-tabs-col v-tabs-left">
<ul class="nav nav-v-tabs flex-column" role="tablist">
<li class="nav-v-item" *ngFor="let tab of tabs" (click)="selectTab(tab)">
<a [class.active]="tab.active" class="nav-v-link" data-toggle="tab" href="#" role="tab">{{tab.title}}</a>
</li>
</ul>
</div>
<div class="v-tabs-col v-tabs-fill">
<div class="v-tabs-content">
<div>
<ng-content></ng-content>
</div>
</div>
</div>
us-tab-verticle.component.ts
import { Component, ContentChildren, QueryList, AfterContentInit } from '#angular/core';
import { VtabContentComponent } from './vtab-content/vtab-content.component';
#Component({
selector: 'us-tab-verticle',
templateUrl: './us-tab-verticle.component.html',
styleUrls: ['./us-tab-verticle.component.scss']
})
export class UsTabVerticleComponent implements AfterContentInit {
#ContentChildren(VtabContentComponent) tabs: QueryList<VtabContentComponent>;
// contentChildren are set
ngAfterContentInit() {
// get all active tabs
const activeTabs = this.tabs.filter((tab) => tab.active);
// if there is no active tab set, activate the first
if (activeTabs.length === 0) {
this.selectTab(this.tabs.first);
}
}
selectTab(tab: VtabContentComponent) {
// deactivate all tabs
this.tabs.toArray().forEach(tab => tab.active = false);
// activate the tab the user has clicked on.
tab.active = true;
}
}
vtab-content.component.html
<div class="tab-content v-tab-content align-content-stretch">
<div [hidden]="!active" class="tab-pane active" role="tabpanel">
<ng-content></ng-content>
</div>
</div>
vtab-content.component.ts
import { Component, Input } from '#angular/core';
import { NgComponentOutlet } from '#angular/common';
#Component({
selector: 'vtab-content',
templateUrl: './vtab-content.component.html',
styleUrls: ['./vtab-content.component.scss']
})
export class VtabContentComponent {
#Input('tabTitle') title: string;
#Input() active = false;
}
I need to load each component when I click the header of the each tabs.
I sow that NgComponentOutlet can use to this kind of situations. But could not get any idea how to implement.
There are many solutions, if you want to improve your solution i suggest the usage of *ngIf. But notice that you destroy and create a new Component every time the *ngIf state changes.
I suggest you to take a look at RouterModule with the use of children attribue.
A little sample : (not sure you can make it work right away but I use similar stuff in my app)
// Router.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
const routes: Routes = [
{
path: '',
children: [
{ path: 'tab1', component: Tab1Component },
{ path: 'tab2', component: Tab2Component },
]
},
{ path: '**', redirectTo: '' } // modify for default tab?
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
export const routedComponents = [Tab1Component, Tab2Component];
// AppModule.ts
imports: [AppRoutingModule]
declarations: [
routedComponents
],
// main.html
<app-header>
<us-tab-verticle>
<div class="tab" [routerLink]="['/tab1']"><a>
<div class="tab" [routerLink]="['/tab2']"><a>
<router-outlet></router-outlet> // the content of your tab components will be displayed below
<app-footer>
This way in my opinion make your app easier to read (declarative routing) instead of having an external service and component for managing the states of your tabs.
I try to reproduce your structure with the usage of the ngComponentOutlet directive. Here is the tab-container:
#Component({
selector: 'tab',
template: ''
})
export class TabComponent {
#Input() title: string;
#Input() contentRef: any;
active = false;
}
This is a very simple component which knows its own tab name, an active state and the body component reference which should be loaded when somebody selects the tab.
Then we create several body components which will be loaded dynamically:
#Component({
selector: 'tab-content',
template: `<p>Hey</p>`
})
export class TabContentComponent {}
#Component({
selector: 'tab-content-alternative',
template: `<p>Hey, this is an alternative content</p>`
})
export class TabContentAlternativeComponent {}
Here is the tabs-container component with tabs rendering and an empty placeholder for dynamic body components:
#Component({
selector: 'tab-container',
template: `
<div class="tab-header">
<div class="tab" *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active">{{tab.title}}</div>
</div>
<div class="tab-content">
<ng-container *ngComponentOutlet="content | async"></ng-container>
</div>
`,
})
export class TabContainerComponent implements AfterContentInit {
#ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
private contentSbj = new BehaviorSubject<BasicContent>(null);
content = this.contentSbj.asObservable();
ngAfterContentInit() {
const activeTabs = this.tabs.filter((tab) => tab.active);
if (activeTabs.length === 0) {
this.selectTab(this.tabs.first);
}
}
selectTab(tab: TabComponent) {
this.tabs.toArray().forEach(tab => tab.active = false);
tab.active = true;
this.contentSbj.next(tab.contentRef);
}
}
And this is how it can be used in some parent component:
import {TabContentComponent} from './tab/tab-content.component'
import {TabContentAlternativeComponent} from './tab/tab-content-alternative.component'
...
#Component({
selector: 'my-app',
template: `
<tab-container>
<tab title="Tab 1" [contentRef]="normalContent"></tab>
<tab title="Tab 2" [contentRef]="alternativeContent"></tab>
</tab-container>
`,
})
export class App {
normalContent = TabContentComponent;
alternativeContent = TabContentAlternativeComponent;
}
Here is working Plunkr
Related
I have an angular2 app that displays chart data from a rest api.
I created the drawing code in my bulletchart.component file.
Now I want to outsource the code into a service.
But it seems like there is only one active instance of the data service.
This is my page content where I want to load the charts.
<div class="col">
<app-bulletchart [ID]="1" [apiAddress]="'http://url1'"></app-bulletchart>
</div>
<div class="col">
<app-bulletchart [ID]="2" [apiAddress]="'http://url2'"></app-bulletchart>
</div>
The template for the app.bulletchart is this:
<div class="panel test{{ID}}">
</div>
In my bulletchart.service file I change the DOM of the app-bulletchart with some methods like this:
initSvg() {
const identifier = '.test' + this.ID;
console.log("ident " + identifier);
this.svg = d3.select(identifier).append('svg')
.attr('class', 'bullet')
There are also methods that update the chart
drawRange() {
console.log("range " + this.ID);
// Update the range rects.
const range = this.g.selectAll('rect.range')
.data(this.ranges);
range.enter().append('rect')
I set the ID property of the bulletchart.service in the ngOnInit in bulletchart.component
But when I now try to use this.bulletchart.drawRange(); this method is only called for ID 1 and not for ID 2.
I don't understand why, because I thought it would do something like this:
create App-Bulletchart (ID=1) -> create instance of bulletchart.service (with ID = 1)
create App-Bulletchart (ID=2) -> create instance of bulletchart.service (with ID = 2)
Edit:
I added providers: [BulletchartService] to my bulletchart.component file and removed it from the app.module and now it works.
But why?!
you may include service provider in the component to ensure service is created for each instance of component
#Component({
...
providers:[BulletchartService]
...
})
Example
#Injectable()
export class AppService{
Id: string;
someMethod(){
console.log(this.Id);
}
}
#Component({
selector: 'my-child',
template: `<h1>Child ID {{Id}}</h1>
<button (click)="invokeService()" >Invoke service</button>
`,
providers:[AppService]
})
export class ChildComponent {
#Input() Id: string;
constructor(private svc: AppService){}
ngOnInit(){
this.svc.Id = this.Id;
}
invokeService(){
this.svc.someMethod();
}
}
#Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>
<my-child [Id]="1" ></my-child>
<my-child [Id]="2" ></my-child>
`
})
export class AppComponent {
name = 'Angular';
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, ChildComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Check this Plunker.
Hope this Helps!!
I'm pretty new to angular, and I have a modal component with a button, on (click) changes the status of the modal from hide to show and it's displayed. however, I want to include this modal on my main component, so I can create a button which displays the modal component over the main component.
this is my modalLink.ts:
import { Component,Input,trigger,state,style,transition,animate } from '#angular/core';
#Component({
templateUrl: 'modalLink.component.html',
styleUrls: ['modalLink.component.css'],
animations:[
trigger('Modal',[
state("show",style({'display':'flex', 'opacity':'1'})),
state("hide",style({'display':'none', 'opacity':'0'})),
transition("show <=> hide", animate( "200ms" ))
])
]
})
export class ModalLink {
private url:string = '';
private modal:string = 'hide';
private objectArea:any = []
private objectLevel:any = []
showModal(){
this.modal = 'show';
}
hideModal(){
this.modal = 'hide';
}
}
and on my mainComponent.ts this is an excerpt of what I have:
import { ModalLink } from './modalLink.component';
#Component({
templateUrl: 'academy.component.html',
styleUrls: ['academy.component.css']
})
export class AcademyComponent {
#ViewChild(ModalLink) modalLink: ModalLink
asdf() {
this.modalLink.showModal();
}
}
and my mainComponent.html which contains the button that calls the asdf function that calls showModal():
<div class="container-organize resource-content">
<button (click)="asdf()" class="btn-floating btn-large btn-new-resource"><i class="ai ai-plus"></i></button>
For your situation, you should consider of taking advantage of Angular-Material.
Angular Material have implement this for you, just use it this way:
import {MdDialog} from '#angular/material';
constructor(public dialog: MdDialog) {}
openDialog() {
this.dialog.open(DialogOverviewExampleDialog); // DialogOverviewExampleDialog is another component
}
here is simple plunker
I have created dynamic component instances by selecting pre-existing components. For example,
#Component({
selector: 'dynamic-component',
template: `<div #container><ng-content></ng-content></div>`
})
export class DynamicComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
public addComponent(ngItem: Type<WidgetComponent>,selectedPlugin:Plugin): WidgetComponent {
let factory = this.compFactoryResolver.resolveComponentFactory(ngItem);
const ref = this.container.createComponent(factory);
const newItem: WidgetComponent = ref.instance;
newItem.pluginId = Math.random() + '';
newItem.plugin = selectedPlugin;
this._elements.push(newItem);
return newItem;
}
}
My pre-existed components are ChartWidget and PatientWidget which extended the class WidgetComponent that I wanted to add in the container. For example,
#Component({
selector: 'chart-widget',
templateUrl: 'chart-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() => ChartWidget) }]
})
export class ChartWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
chart-widget.compoment.html (using primeng Panel)
<p-panel [style]="{'margin-bottom':'20px'}">
<p-header>
<div class="ui-helper-clearfix">
<span class="ui-panel-title" style="font-size:14px;display:inline-block;margin-top:2px">Chart Widget</span>
<div class="ui-toolbar-group-right">
<button pButton type="button" icon="fa-window-minimize" (click)="minimize()"</button>
<button pButton type="button" icon="fa-refresh" (click)="refresh()"></button>
<button pButton type="button" icon="fa-expand" (click)="expand()" ></button>
<button pButton type="button" (click)="close()" icon="fa-window-close"></button>
</div>
</div>
</p-header>
some data
</p-panel>
data-widget.compoment.html (same as chart-widget using primeng Panel)
#Component({
selector: 'data-widget',
templateUrl: 'data-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() =>DataWidget) }]
})
export class DataWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
WidgetComponent.ts
#Component({
selector: 'widget',
template: '<ng-content></ng-content>'
})
export class WidgetComponent{
}
Now I added the components by selecting a component from the existed components (e.g. chart-widget and data-widget) in the following way and stored the instances into an array.
#Component({
templateUrl: 'main.component.html',
entryComponents: [ChartWidget, DataWidget],
})
export class MainComponent implements OnInit {
private elements: Array<WidgetComponent>=[];
private WidgetClasses = {
'ChartWidget': ChartWidget,
'DataWidget': DataWidget
}
#ViewChild(DynamicComponent) dynamicComponent: DynamicComponent;
addComponent(): void{
let ref= this.dynamicComponent.addComponent(this.WidgetClasses[this.selectedComponent], this.selectedComponent);
this.elements.push(ref);
this.dynamicComponent.resetContainer();
}
}
Now, I am facing problem to render the components using innerHtml in main.component.html. It render the html but I am not able to use button click event or other event on it. I have also tried to render chart using primeng but its also not working.
main.component.html
<dynamic-component [hidden]="true" ></dynamic-component>
<widget *ngFor="let item of elements">
<div [innerHTML]="item._ngEl.nativeElement.innerHTML | sanitizeHtml">
</div>
</widget>
I have also implemented a sanitizeHtml Pipe but its giving still same result. So, as I understand innerHTML is only showing the html data but I can't use any button event as well as the js chart. I have also tried to show the items like this {{item}} under tag. But it display like a text [object object]. So, could anyone give a solution for it? How can I render the components allowing the button events and js chart? Thanks.
EDIT: See my Plunker here https://plnkr.co/edit/lugU2pPsSBd3XhPHiUP1?p=preview
You can see here, it is possible to add chart or data widget dynamically and I am showing it using innerHTML. So, the button events are not working here. If I coding like {{item}} then it shows [object object] text. You can also see in console the component array data. The main Question is, How can I active the button events on it (e.g. if i click close or refresh button then it will call the related functions)?
I would create structural directive like:
view.directive.ts
import { ViewRef, Directive, Input, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[view]'
})
export class ViewDirective {
constructor(private vcRef: ViewContainerRef) {}
#Input()
set view(view: ViewRef) {
this.vcRef.clear();
this.vcRef.insert(view);
}
ngOnDestroy() {
this.vcRef.clear()
}
}
then
app.component.ts
private elements: Array<{ view: ViewRef, component: WidgetComponent}> = [];
...
addComponent(widget: string ): void{
let component = this.dynamicComponent.addComponent(this.WidgetClasses[widget]);
let view: ViewRef = this.dynamicComponent.container.detach(0);
this.elements.push({view,component});
this.dynamicComponent.resetContainer();
}
and
app.component.html
<widget *ngFor="let item of elements">
<ng-container *view="item.view"></ng-container>
</widget>
So i have just moved view from dynamic component container to desired place.
Plunker Example
In my app-root component I have router-outlet in container with some styles.
I have route:
{
path: 'some-path',
component: ChildContainer,
data: { variable:'variable' },
}
And I can to get variable in ChildContainer, but I need it in AppRoot. So, from documentation I can get it from child, but if I do this in AppRoot constructor:
const state: RouterState = router.routerState;
const root: ActivatedRoute = state.root;
const child = root.firstChild;
and console.log(root, child) - child is null, and root contains correct child (invoke property getter).
So, how can I get variable in AppRoot?
You may tap into activate event to get reference of instantiated component inside the router outlet.
Check This SO question
#Component({
selector: 'my-app',
template: `<h3 class="title">Basic Angular 2</h3>
<router-outlet (activate)="onActivate($event)" ></router-outlet>
`
})
export class AppComponent {
constructor(){}
onActivate(componentRef){
componentRef.sayhello();
}
}
#Component({
selector: 'my-app',
template: `<h3 class="title">Dashboard</h3>
`
})
export class DashboardComponent {
constructor(){}
sayhello(){
console.log('hello!!');
}
}
Here is the Plunker!!
Update
expose ActivatedRoute as a public property and once you have the routed component reference, subscribe to data,
onActivate(componentRef){
componentRef.route.data.subsribe(data => {
console.log(data);
});
}
For the life of me I cannot figure out why I cannot either emit or capture some data. The toggleNavigation() fires, but I'm not sure if the .emit() is actually working.
Eventually I want to collapse and expand the navigation, but for now I just want to understand how to send data from the navigation.component to the app.component.
Here is a Plunker link
app.component
import { Component } from '#angular/core';
import { PasNavigationComponent } from './shared/index';
#Component({
moduleId: module.id,
selector: 'pas-app',
template: `
<pas-navigation
(toggle)="toggleNavigation($event);">
</pas-navigation>
<section id="pas-wrapper">
<pas-dashboard></pas-dashboard>
</section>
`,
directives: [PasNavigationComponent]
})
export class AppComponent {
toggleNavigation(data) {
console.log('event', data);
}
}
pas-navigation.component
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'pas-navigation',
template: `
<nav>
<div class="navigation-header">
<i class="fa fa-bars" (click)="toggleNavigation()"></i>
</div>
</nav>
`
})
export class PasNavigationComponent {
#Output('toggle') navToggle = new EventEmitter();
toggleNavigation() {
this.navToggle.emit('my data to emit');
}
}
EDIT
I added Pankaj Parkar's suggestions.
You just need to specify $event parameter on method, so whatever emitted on navToggle output value, that data will be available inside AppComponent 'stoggleNavigationmethod$event` parameter.
<pas-navigation
(toggle)="toggleNavigation($event);">
</pas-navigation>
AppComponent
toggleNavigation(data) {
// you can get emitted data here by `pas-navigation` component
console.log('event', data);
}
Forked Plunkr