Angular2 dynamically added elements - html

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();
}

Related

The selector "app-tool-tip" did not match any elements how do I fix this?

I need to make a SIMPLE tool tip and I've implemented the following from here: https://ng-bootstrap.github.io/#/components/tooltip/examples
What I'm getting is this error: The selector "app-tool-tip" did not match any elements
Here's my tooltip component:
Tooltip Module:
import {NgModule} from '#angular/core';
import {FormsModule} from '#angular/forms';
import {BrowserModule} from '#angular/platform-browser';
import {NgbModule} from '#ng-bootstrap/ng-bootstrap';
import {NgbdTooltipComponent} from './tool-tip.component';
#NgModule({
imports: [BrowserModule, FormsModule, NgbModule],
declarations: [NgbdTooltipComponent],
exports: [NgbdTooltipComponent],
bootstrap: [NgbdTooltipComponent]
})
export class NgbdTooltipModule {}
Tooltip Component
import {Component, OnInit, Input} from '#angular/core';
#Component({
selector: 'app-tool-tip',
templateUrl: './tool-tip.component.html',
styleUrls: ['./tool-tip.component.scss']
})
export class NgbdTooltipComponent implements OnInit {
#Input() toolTip: string;
constructor() {}
ngOnInit() {
}
}
Tooltip HTML
<button type="button" class="btn btn-outline-secondary mr-2"
placement="top" ngbTooltip="Tooltip on top">
Tooltip on top
</button>
<button type="button" class="btn btn-outline-secondary mr-2"
placement="right" ngbTooltip="Tooltip on right">
Tooltip on right
</button>
<button type="button" class="btn btn-outline-secondary mr-2"
placement="bottom" ngbTooltip="Tooltip on bottom">
Tooltip on bottom
</button>
<button type="button" class="btn btn-outline-secondary mr-2"
placement="left" ngbTooltip="Tooltip on left">
Tooltip on left
</button>
This is my app-component.html
<div
*ngIf="loading"
class="loading d-flex align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md pt-3">
<div class="loading__brand"></div>
<h1 class="h3 my-3 text-center text-muted">Loading...</h1>
</div>
</div>
</div>
</div>
<app-error-message *ngIf="showError" ng-class="{fade:doFade}" [errorMessage]="errorMessage"></app-error-message>
<app-tool-tip></app-tool-tip>
And this is being imported in my app.module.ts
import {NgbdTooltipModule} from './components/tool-tip/tool-tip.module';
This is my main.ts
platformBrowserDynamic()
.bootstrapModule(NgbdTooltipModule)
.then(ref => {
// Ensure Angular destroys itself on hot reloads.
if (window[<any>'ngRef']) {
window['ngRef'].destroy();
}
window['ngRef'] = ref;
// Otherwise, log the boot error
})
.catch(err => console.error(err));
How do I fix this and why can't it find: app-tool-tip selector?
I don't see in your code provision for AppComponent & AppModule. You should bootstrap your AppModule (in main.ts), and import your NgbdTooltipModule into app.module.ts file. See below for an example. I hope it helps.
Stackblitz Project: https://stackblitz.com/edit/ngbd-tooltips
// main.ts Main Entry
import { enableProdMode } from '#angular/core';
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule) // Bootstrap AppModule
.then(ref => {
// Ensure Angular destroys itself on hot reloads.
if (window[<any>'ngRef']) {
window['ngRef'].destroy();
}
window['ngRef'] = ref;
// Otherwise, log the boot error
})
.catch(err => console.error(err));
// app.module.ts - App Module:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgbdTooltipModule } from './ngbd-tooltip/ngbd-tooltip.module';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule,
NgbdTooltipModule // Import NgbdTooltipModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// ngbd-tooltip.module.ts - Tooltip Module:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { NgbdTooltipComponent } from './ngbd-tooltip.component';
#NgModule({
declarations: [NgbdTooltipComponent], // Declare NgbdTooltipComponent
imports: [
CommonModule
],
exports: [NgbdTooltipComponent] // Export NgbdTooltipComponent
})
export class NgbdTooltipModule { }

How to pass and display Calculation result on a New view/component in Angular?

I created a simple calculator app. I have two component: Calculator and Result and use Angular router between them. Now I want to do this : When I perform a calculation in Calculation component, the result will be passed and displayed in another Result component. Can you show me some ways for this ?
calculator.component.ts
#Component({
selector: 'app-calculator',
templateUrl: './calculator.component.html',
styleUrls: ['./calculator.component.css']
})
export class CalculatorComponent implements OnInit {
public number1: number;
public number2: number;
public result: number;
constructor() {
}
sum() {
this.result = this.number1 + this.number2;
}
diff() {
this.result = this.number1 - this.number2;
}
mult() {
this.result = this.number1 * this.number2;
}
divi() {
this.result = this.number1 / this.number2;
}
ngOnInit(): void {
}
}
calculator.component.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="card">
<div class="card-header">CALCULATOR</div>
<div class="card-body">
<div class="form-group value">
<div class="form-group row">
<label class="col-md-2 col-form-label">Value 1:</label>
<div class="col-md-10 input-1">
<input [(ngModel)]='number1' class="form-control inp" type="number" name="num1">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label">Value 2:</label>
<div class="col-md-10 input-2">
<input [(ngModel)]='number2' class="form-control inp" type="number" name="num2">
</div>
</div>
</div>
<div class="buttons">
<br>
<button class="butt" (click)='sum()'> + </button>
<button class="butt" (click)='diff()'> - </button>
<button class="butt" (click)='mult()'> x </button>
<button class="butt" (click)='divi()'> / </button>
<br><br><br>
</div>
{{result}}
</div>
</div>
</body>
</html>
app-routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { CalculatorComponent } from './calculator/calculator.component';
import { ResultComponent } from './result/result.component';
const appRoutes: Routes = [
{ path: 'calculator', component: CalculatorComponent },
{ path: 'result', component: ResultComponent }
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)],
exports: [RouterModule]
})
export class AppRoutingModule { }
app.component.html
<h1>Angular Router</h1>
<nav>
<a routerLink="/calculator" routerLinkActive="active">Calculator</a><br>
<a routerLink="/result" routerLinkActive="active">Result</a>
</nav>
<router-outlet></router-outlet>
data.service.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
}
There are multiple ways to do this,
The simplest way can be to use state while navigating to result route from calculator route.
this.router.navigate(['result'], { state: { result } });
This will put your result in window.history.state object, which then you can access from result component.
ngOnInit() {
this.resultValue = window.history.state.result;
}
Demo
2. You can also store the result in shared service and then access that variable from result component. Inject the service wherever required and store the retrieve data from it.
#Injectable()
export class SharedServiceService {
storeValue: number = 0;
constructor() { }
}

Disable scrolling in typescript as soon as property is true

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 :)

is there a way i can fix my accordion in angular?

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)

Angular 4 MaterializeCSS ngfor and tabs

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!