I am trying to make dynamically tabs. So I use materialize CSS to create that in my Angular 4 App, I can't seem to get it working. I try this:
<div>
<ul class="tabs tabs-fixed-width">
<div class="indicator" style="z-index:1; background-color: #1ABFB4 !important;"></div>
<li *ngFor="let entry of data" class="tab">{{entry.code}}</li>
</ul>
</div>
<div *ngFor="let item of data" id="{{item.code}}" class="col s12">{{item.description}}</div>
</div>
This creates the tabs correctly but they don't respond with a page and every tab has all the divs, so when I have 4 tabs, I get 4 descriptions under every tab. How can I make tabs with ngFor?
//make a wrapper like below
import {
Component,
ElementRef,
AfterViewInit,
NgZone,
Input,
Output
} from '#angular/core';
declare var $: any;
#Component({
selector: 'tabs',
styles: [`
.carousel .carousel-item {
display: block; width: 200px; height:200px;
position: relative; top: 0; left: 0;
}
`],
template: `<ng-content></ng-content>`
})
export class MyTabsComponent implements AfterViewInit {
$tabs: any;
#Output() onShow: EventEmitter<any> = new EventEmitter();
#Input() swipeable = false;
constructor(private el: ElementRef, private zone: NgZone) { }
ngAfterViewInit() {
this.zone
.runOutsideAngular(() => {
this.$tabs = $(this.el.nativeElement);
this.$tabs.find('ul.tabs')
.on('click', 'a', ((tab) => {
this.zone.run(() => { // detect change and use
this.onShow.emit({ tab, tabRef: this.$tabs });
});
}).bind(this))
.tabs({// initialize your tabs outside angular
'responsiveThreshold': 1920,
'swipeable': this.swipeable
});
});
}
}
// use the wrapper component and provide the data
#Component({
select: 'app-root',
template: `
<tabs (onShow)="onTabOpen($event)>
<div>
<ul class="tabs tabs-fixed-width">
<div class="indicator" style="z-index:1; background-color:
#1ABFB4 !important;"></div>
<li *ngFor="let entry of data" class="tab"><a [attr.href]="'#'+
entry.code">{{entry.code}}</a></li>
</ul>
</div>
</tabs>
<div *ngFor="let item of data" [attr.id]="item.code" class="col s12">
{{item.description}}</div>
`
})
class AppComponent {
data: [
{
code: 'first',
description: 'I am first tab'
},
{
code: 'second',
description: 'I am second tab'
}
]
onTabOpen(event:any) {
// do some stuff
}
}
It is working for me hope it works for you as well!
Related
I have a hamburger inside of my header - as soon as it is clicked, I wan't to disable scroll - since the hamburger is moving on scroll.
I tried to set the hamburger to position: fixed. But this one changed the position of its neighbour, which looked weird.
Is there a way to realize this in typescript - so that as soon clicked is true, scrolling is disabled. Or is there a better approach?
https://stackblitz.com/edit/angular-beiajz
export class HeaderComponent implements OnInit {
clicked = false;
onClick(): void {
this.clicked = !this.clicked;
}
constructor() { }
ngOnInit(): void {
}
}
<div class="container-fluid mobile position-absolute">
<div class="row m-0 w-100">
<div class="col-6 offset-3 justify-content-center d-flex">
<a class="align-self-center" routerLink="">
<h1>NØREBRO STUDIOS</h1>
</a>
</div>
<div class="col-3">
<button class="hamburger hamburger--collapse" (click)="onClick()" [class.is-active]="clicked" type="button">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</div>
<div class="container-fluid position-fixed min-vh-100 px-0 slide-in" [class.get-active]="clicked">
</div>
</div>
</div>
One of the way is passing a event from the child to parent whenever click is made on the hamburger menu and changing the parent class CSS
in app.component.html
<app-test (hamburgerClick)="hamburgerClickEvent($event)"></app-test>
<div [ngClass]="{
'container-fluid-overlay': overlayFlag === true,
'container-fluid': overlayFlag === false }">
</div>
in app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
overlayFlag = false; // overlay flag..onload overlay is hidden
public hamburgerClickEvent(e:boolean):void{
this.overlayFlag= e;
}
}
in app.component.css
.container-fluid {
min-height: 200vh;
}
.container-fluid-overlay {
height: auto;
}
in test.component.ts
import { Component, OnInit , Output, EventEmitter} from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
#Output() hamburgerClick = new EventEmitter();//emitter
clicked = false;
onClick(): void {
this.clicked = !this.clicked;
this.hamburgerClick.emit(this.clicked); //emitting clicked event to parent
}
constructor() { }
ngOnInit(): void {
}
}
Hope it helps :)
I try to modify a part of HTML in a component as we do with JS "innerHTML" in a angular app. I'm a beginner with angular, maybe I missed something important.
Actually look like that (typescript, html (from the same component)):
import { Component, OnInit, Input } from '#angular/core';
import { GraphService } from '../services/graph.service';
#Component({
selector: 'app-graph-content',
templateUrl: './graph-content.component.html',
styleUrls: ['./graph-content.component.scss']
})
export class GraphContentComponent implements OnInit {
#Input() graphName: string;
#Input() index: number;
#Input() id: number;
#Input() graphContent: any;
graphI:string;
constructor(private graphService:GraphService) { }
testF(){
var inputGraph = this.graphService.refreshGraph(this.graphContent);
//inputGraph should go in DOM and look like : "<div class=\"progress-fill\" style=\"background-color: black;\"><span>XX%</span></div>"
}
ngOnInit() {
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div class="list-group-item list-group-item-action flex-column align-items-start list-group-flush">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1"> {{ graphName }}</h5>
</div>
<div class="horizontal" style="height: 100px;">
<div class="progress-track" style="display: flex; flex-wrap: nowrap; justify-content: flex-start;" id="graphInput">
<!--here comes the content-->
</div>
<button (click)="testF()">react</button>
</div>
So inputGraph must go in graphInput div. Is there a simple way to do it, would it be better to create a component for this part?
You can use the property binding for innerHTML like [innerHTML].
<div [innerHTML]="htmlstring">
and in your component.ts file
this.htmlstring = `<p> Hello</p>`
¿What kind of data came from this.graphService.refreshGraph(this.graphContent)?
¿It´s JSON data?
Maybe you could make a component named app-input-graph, with an #Input inputGraph and this html code:
<div class="progress-fill" style="background-color: black;">
<span>{{inputGraph}}</span>
</div>
And in app-graph-content:
<app-input-graph [inputGraph]="inputGraph"><app-input-graph>
I'm new to angular and I tried to make an accordion component, and it' didn't work like I wanted it to, here's my html code.
<div class="faq-item-container">
<h1 class="mt-1 mb-5"><strong>Frequently Aksed Questions</strong></h1>
<div class="row" (click)="toggleDetail(); toggleIcon();" *ngFor= "let faq of faqs">
<div class="col my-2">
<h3> {{faq.title}} <a><fa-icon [icon]="faChevronDown" class="float-right"></fa-icon></a></h3>
</div>
<div class="col-12" *ngIf="showDetail">
<div class="faq-detail-container mt-1">
<div class="col-12">
<p><small>
{{faq.content}}
</small></p>
</div>
</div>
</div>
</div>
</div>
and here's the ts code
import { Component, OnInit } from '#angular/core';
import {faChevronUp, faChevronDown, IconDefinition, faSquare} from '#fortawesome/free-solid-svg-icons';
#Component({
selector: 'app-jobs-faq',
templateUrl: './jobs-faq.component.html',
styleUrls: ['./jobs-faq.component.scss']
})
export class JobsFaqComponent implements OnInit {
faChevronUp: IconDefinition = faChevronUp;
faChevronDown: IconDefinition = faChevronDown;
showDetail: boolean;
faqs = [
{
id: 1,
title: 'faq1',
content: 'content1'
},
{
id: 2,
title: 'faq2',
content: 'content2'
},
{
id: 3,
title: 'faq3',
content: 'content3'
}
];
constructor() {
this.showDetail = false;
}
toggleDetail(): void {
this.showDetail = !this.showDetail;
}
toggleIcon() {
if (this.faChevronDown === faChevronDown) {
this.faChevronDown = faChevronUp;
} else {
this.faChevronDown = faChevronDown;
}
}
ngOnInit() {
}
}
The problem is when I click faq1, the others also collpasing, yes I know it's because I called the same function, and that is what I want to ask about, how to call the function separately to make this accordion working like it's supposed to be? thanks.
It depends on whether you want to close all other sections when you click one or not, but a solution could look somewhat like this:
<div class="faq-item-container">
<h1 class="mt-1 mb-5"><strong>Frequently Aksed Questions</strong></h1>
<div class="row" (click)="toggleDetail(faq.id); toggleIcon();" *ngFor= "let faq of faqs">
<div class="col my-2">
<h3> {{faq.title}} <a><fa-icon [icon]="faChevronDown" class="float-right"></fa-icon></a></h3>
</div>
<div class="col-12" *ngIf="faq.showDetail">
<div class="faq-detail-container mt-1">
<div class="col-12">
<p><small>
{{faq.content}}
</small></p>
</div>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '#angular/core';
import {faChevronUp, faChevronDown, IconDefinition, faSquare} from '#fortawesome/free-solid-svg-icons';
#Component({
selector: 'app-jobs-faq',
templateUrl: './jobs-faq.component.html',
styleUrls: ['./jobs-faq.component.scss']
})
export class JobsFaqComponent implements OnInit {
faChevronUp: IconDefinition = faChevronUp;
faChevronDown: IconDefinition = faChevronDown;
faqs = [
{
id: 1,
title: 'faq1',
content: 'content1',
showDetail: false
},
{
id: 2,
title: 'faq2',
content: 'content2',
showDetail: false
},
{
id: 3,
title: 'faq3',
content: 'content3',
showDetail: false
}
];
toggleDetail(faqId: number): void {
this.faqs = this.faqs.map(faq => {
faq.showDetail = (faq.id == faqId) ? !faq.showDetail : false;
return faq;
});
}
toggleIcon() {
if (this.faChevronDown === faChevronDown) {
this.faChevronDown = faChevronUp;
} else {
this.faChevronDown = faChevronDown;
}
}
ngOnInit() {
}
}
Note that your [icon]="faChevronDown" should be based on the faq in the thes scope of your *ngFor. I'll leave it to you as practice to find a solution for that. (Hint: you could use a ternary operation based on faq.showDetail)
I'm trying to communicate between two components, but my #Output() doesn't seem to be working when a button is clicked. Accessing the function is not the issue as I can get a response from the console.
child-comp.component.html:
<div class="container" style="text-align: center" style="padding-top: 10px">
<label>The Child text is: {{goodbye}} </label>
<p></p>
<p>The parent input is : {{state}} </p>
<button class="primary" (click)='sendToParent()'>Send to Parent</button>
</div>
parent-comp.component.html:
<div class="container" style="text-align: center" style="padding-top:
10px">
<label>The Parent text is: {{hello}}</label>
<p></p>
<p>The Child input is : {{goodbye}} </p>
</div>
<hr>
<app-child-comp [state]="hello" (event)="setDataFromChild()"></app-child-
comp>
child-comp.component.html:
<div class="container" style="text-align: center" style="padding-top:
10px">
<label>The Child text is: {{goodbye}} </label>
<p></p>
<p>The parent input is : {{state}} </p>
<button class="primary" (click)='sendToParent()'>Send to Parent</button>
</div>
child-comp.component.ts:
import { Component, OnInit, Input, Output, EventEmitter} from
'#angular/core';
#Component({
selector: 'app-child-comp',
templateUrl: './child-comp.component.html',
styleUrls: ['./child-comp.component.css'],
})
export class ChildCompComponent implements OnInit {
#Input() public state: string;
#Output() event: EventEmitter<string> = new EventEmitter();
public goodbye = 'goodbye';
constructor() { }
ngOnInit() {
}
sendToParent() {
this.event.emit(this.goodbye);
}
}
parent-comp.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-parent-comp',
templateUrl: './parent-comp.component.html',
styleUrls: ['./parent-comp.component.css']
})
export class ParentCompComponent implements OnInit {
public hello = 'hello';
public goodbye: string;
constructor() { }
ngOnInit() {
}
public setDataFromChild(data) {
this.goodbye = data;
console.log('event emitted');
}
}
You dont take data comming from EventEmitter:
Change this:
(event)="setDataFromChild()"
To:
(event)="setDataFromChild($event)"
I'm trying to use the dynamic component provided by #GünterZöchbauer here Angular 2 dynamic tabs with user-click chosen components to create rows and columns. So far I got successfully the rows added but still can't get the columns created inside the rows.
Here is my code:
DesignerModule
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { MaterializeModule } from '../../shared/materialize/materialize.module';
import { DesignerComponent } from './designer.component';
import { RowComponent } from './designer.component';
import { ColumnComponent } from './designer.component';
import { DynWrapperComponent } from './dyn-wrapper.component';
#NgModule({
imports: [
CommonModule,
MaterializeModule,
],
declarations: [
DesignerComponent,
DynWrapperComponent,
RowComponent,
ColumnComponent,
],
entryComponents: [
RowComponent,
ColumnComponent,
],
providers: [
]
})
export class DesignerModule {}
DynWrapperComponent
import { Component, Compiler, ViewContainerRef, ViewChild, Input, ElementRef,
ComponentRef, ComponentFactory, ComponentFactoryResolver} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
// Helper component to add dynamic components
#Component({
moduleId: module.id,
selector: 'dcl-wrapper',
template: `<div #target></div>`
})
export class DynWrapperComponent {
#ViewChild('target', {read: ViewContainerRef}) target: any;
#Input() type: any;
cmpRef:ComponentRef<any>;
private isViewInitialized:boolean = false;
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
private el: ElementRef) {}
updateComponent() {
if(!this.isViewInitialized) {
return;
}
if(this.cmpRef) {
this.cmpRef.destroy();
}
let factory = this.componentFactoryResolver.resolveComponentFactory(this.type);
//this.resolver.resolveComponent(this.type).then((factory:ComponentFactory<any>) => {
this.cmpRef = this.target.createComponent(factory)
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
DesignerComponent
import { Component, ViewChild, ElementRef, ContentChildren } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'my-row',
templateUrl: 'row.component.html',
styles: [
`.row:hover {
border: 3px dashed #880e4f ;
}
`
]
})
export class RowComponent {
colIndex: number = 0;
colList: Object[] = [];
addColumn() {
this.colList.splice(this.colIndex, 0, ColumnComponent);
this.colIndex++;
}
removeColumn(colIdx: number) {
this.colList.splice(colIdx, 1);
}
}
#Component({
moduleId: module.id,
selector: 'my-column',
templateUrl: 'column.component.html',
styles: [
`.col:hover {
border: 3px solid #304ffe;
}
`
]
})
export class ColumnComponent {
}
#Component({
moduleId: module.id,
selector: 'my-designer',
templateUrl: 'designer.component.html',
})
export class DesignerComponent {
#ViewChild('builder') builder:ElementRef;
elementIndex: number = 0;
list: Object[] = [];
ngAfterViewInit() {
}
addRow() {
this.list.splice(this.elementIndex, 0, RowComponent);
this.elementIndex++;
}
remove(idx: number) {
this.list.splice(idx, 1);
}
}
DesignerComponent.html
<div #builder class="row">
<div class="s1 teal lighten-2">
<p class="flow-text">teste do html builder</p>
<div *ngFor="let row of list; let idx = index" >
<p class="flow-text">Linha {{idx}}</p>
<dcl-wrapper [type]="row"></dcl-wrapper>
<a class="btn-floating btn-small waves-effect waves-light purple" (click)="remove(idx)"><i class="material-icons">remove</i></a>
</div>
</div>
</div>
<a class="btn-floating btn-large waves-effect waves-light red" (click)="addRow()"><i class="material-icons">add</i></a>
RowComponent.html
<div #row class="row">
<div class="s12 teal lighten-2">
<p class="flow-text">adicionando linha no html builder</p>
</div>
<div *ngFor="let col of colList; let colIndex = index">
<p>Column</p>
<dcl-wrapper [type]="col"></dcl-wrapper>
</div>
<a class="btn-floating btn-small waves-effect waves-light waves-teal" (click)="addColumn()"><i class="material-icons">view_column</i></a>
</div>
ColumnComponent.html
<div class="col s1 purple lighten-2">
<p class="flow-text">column added ....</p>
</div>
This approach is generating the following error:
Expression has changed after it was checked. Previous value:
'CD_INIT_VALUE'. Current value:
Did it anyone get this working as nested elements?
Thanks very much for the help!
Try to use ngAfterContentInit hook instead of ngAfterViewInit in your DynWrapperComponent:
dyn-wrapper.component.ts
ngAfterContentInit() {
this.isViewInitialized = true;
this.updateComponent();
}