Angular 2 How to share variables between components - javascript

I'm trying to figure out how to switch a variable in a group of child components
I have this component for a editable form control which switches between view states
import {
Component,
Input,
ElementRef,
ViewChild,
Renderer,
forwardRef,
OnInit
} from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
const INLINE_EDIT_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InlineEditComponent),
multi: true
};
#Component({
selector: 'inline-edit',
templateUrl: 'inline-edit.html',
providers: [INLINE_EDIT_CONTROL_VALUE_ACCESSOR],
})
export class InlineEditComponent implements ControlValueAccessor, OnInit {
#ViewChild('inlineEditControl') inlineEditControl: ElementRef;
#Input() label: string = '';
#Input() type: string = 'text';
#Input() required: boolean = false;
#Input() disabled: boolean = false;
private _value: string = '';
private preValue: string = '';
public editing: boolean = false;
public onChange: any = Function.prototype;
public onTouched: any = Function.prototype;
get value(): any {
return this._value;
}
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: any) {
this._value = value;
}
public registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
public registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
constructor(element: ElementRef, private _renderer: Renderer) {
}
ngOnInit() {
}
}
<div>
<div [hidden]="!editing">
<input #inlineEditControl [required]="required" [name]="value" [(ngModel)]="value" [type]="type" [placeholder]="label" />
</div>
<div [hidden]="editing">
<label class="block bold">{{label}}</label>
<div tabindex="0" class="inline-edit">{{value}} </div>
</div>
</div>
I'm trying to create a simple directive to consume these components and change the editing flag to true
export class EditForm {
//I want to do something like this:
public toggleEdit(fn: () => {}): void {
var editableFormControls = $('#selector: 'inline-edit');
editableFormControls.forEach(control => control.editing = true)
}
}
I want to grab all of the ediiitable form controls and set the editing flag in all of them to true, how can I do this?

You might need to implement a service that keeps the state and all child component subscribe to the state and parent push changes there.
import {Component, NgModule, VERSION, Input} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
export class EditableService {
subject = new BehaviorSubject(true);
getAsObservable() {
return this.subject.asObservable();
}
}
#Component({
selector:'editable',
template: '<div>i am editable {{ x | async}}</div>'
})
export class Editable {
constructor(private editableService: EditableService) {
this.x = editableService.getAsObservable();
}
}
#Component({
selector: 'my-app',
template: `
<editable></editable>
<editable></editable>
<hr/>
<button (click)="change()">change</button>
`,
providers: [EditableService]
})
export class App {
change() {
this.editableService.subject.next(false);
}
constructor(private editableService: EditableService) {
this.name = `Angular! v${VERSION.full}`;
}
}

Related

Communication between children in angular

I am making a list of students. The input field, add button, and update button are inside one child and in other child there is the list with the delete and edit buttons. Both are handled in the parent component.
When I click edit button, I would like the input filed to have a value from that list and to be able to update the list.
parent html
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
>
</ul>
parent .ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = '';
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
console.log('app student :>> ', this.student);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[value]="name"
(input)="oninputSetName($event)"
/>
{{ student }}
{{ name }}
<button (click)="onclickEmitNewName()">Add</button>
<button>Update</button>
child 1 .ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student = '';
oninputSetName(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
}
updateInput() {
let obj = { name: this.student };
console.log('list-form student :>> ', this.student);
}
}
child 2 html
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
child 2 .ts
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = { index: this.index, name: this.name };
this.editNameById.emit(obj);
}
}
Or even in a more elegant way, you could use a helper service to solve your communication issue.
Below you could find a sample service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class MessageService {
private messageSource = new Subject<string>();
currentMessage = this.messageSource.asObservable();
constructor() {}
changeMessage(message: string) {
this.messageSource.next(message);
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-sender',
template: `
<button (click)="sendMessage()">Send Message</button>
`
})
export class SenderComponent {
constructor(private messageService: MessageService) {}
sendMessage() {
this.messageService.changeMessage('Hello from Sender Component');
}
}
import { Component } from '#angular/core';
import { MessageService } from './message.service';
#Component({
selector: 'app-receiver',
template: `
<p>{{ message }}</p>
`
})
export class ReceiverComponent {
message: string;
constructor(private messageService: MessageService) {
this.messageService.currentMessage.subscribe(message => {
this.message = message;
});
}
}
Since angular is Pass By reference. You can take advantage of that. when you do that you don't even have to emit the changed value.
For Example, In your code at Child 1:
Rather than emitting a local variable. All you can do is assign the value to #Input.
Here is a example:
#Input existingStudentName: string;
localName: string = existingStudentName;
onUserUpdate(){
existingStudentName = localName;
//No need to emmit since existingStudentName is updated here
//It will update in parent or anyone who refer it
}
<input type="text" [(ng-Model)]="localName">
<input type="button" (click)="onUserUpdate()">
Parent Html
<section>
<!-- input field where i enter my data -->
<app-list-form
(newName)="onemitAddNewName($event)"
[student]="student"
(updatedName)="updateThisNameInList($event)"
></app-list-form>
<!-- rendering my list -->
<ul>
<app-list-item
*ngFor="let studentName of students; let i = index"
[name]="studentName"
[index]="i"
(deleteNameByIndex)="onemitDeleteNameByIndex($event)"
(editNameById)="onemitEditNameById($event)"
[
></app-list-item>
</ul>
</section>
Parent ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
students = ['huzaifa', 'hunzila', 'waqar', 'sibte', 'shahzeen'];
student = null;
onemitAddNewName(newName: string) {
this.students.push(newName);
}
onemitDeleteNameByIndex(index: number) {
this.students.splice(index, 1);
}
onemitEditNameById(student: any) {
this.student = student;
}
updateThisNameInList(student: any) {
let newName = student.name;
let index = student.index;
this.students.splice(index, 1, newName);
}
}
child 1 html
<input
type="text"
placeholder="enter name"
[(ngModel)]="name"
(input)="oninputSetName($event)"
/>
<!-- {{ student?.name ?? "" }}
{{ name }} -->
<button (click)="onclickEmitNewName()">Add</button>
<button (click)="onclickEmitUpdateName()">Update</button>
child 1 ts
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '#angular/core';
#Component({
selector: 'app-list-form',
templateUrl: './list-form.component.html',
styleUrls: ['./list-form.component.css'],
})
export class ListFormComponent implements OnChanges {
name = '';
#Output() newName = new EventEmitter<string>();
#Input() student: any = null;
#Output() updatedName = new EventEmitter<any>();
oninputSetName(event: any) {
this.name = event.target.value;
}
ngOnChanges(changes: SimpleChanges): void {
console.log('list-form: changes happen ', changes);
this.name = changes['student']?.currentValue?.name ?? '';
}
change(event: any) {
this.name = event.target.value;
}
onclickEmitNewName() {
this.newName.emit(this.name);
this.name = '';
}
onclickEmitUpdateName() {
// if (this.name == '') return;
if (!this.name) return;
this.updatedName.emit({
name: this.name,
index: this.student.index,
});
}
}
child 2 html
<li>
{{ name }} -- {{ index }}
<button (click)="onclickDeleteName()">delete</button>
<button (click)="onclickEditName()">edit</button>
</li>
child 2 ts
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css'],
})
export class ListItemComponent {
#Input() name = '';
#Input() index = 0;
#Output() deleteNameByIndex = new EventEmitter<number>();
#Output() editNameById = new EventEmitter<any>();
onclickDeleteName() {
this.deleteNameByIndex.emit(this.index);
}
onclickEditName() {
let obj = {
index: this.index,
name: this.name,
};
this.editNameById.emit(obj);
}
}

Angular 7 - Calling a function inside of a sibling component after sending an Output() from the sibling

I have three components. A parent component (dashboard.component.ts) which houses two siblings (poll-form.component.ts and poll.component.ts). One of the siblings, poll-component sends an Output() back to the parent called (editEvent)="editPoll($event)". When this Output() is sent I'd like that to trigger a function inside of the other sibling poll-form.component instead of triggering the function in the parent dashboard.component. What's the correct way of doing this?
My current implementation "sort of" works. It fires the function in the parent and then the parent imports the component and calls the function in the sibling component. The issue, however, is that sibling component skips the ngOnInit() and doesn't have access to the component variables needed within the function.
Ultimately what I'm trying to do is break out the form into its own component but the act of opening the form is triggered by a sibling component.
dashboard.component.html
<div class="page">
<div style="margin-bottom: 20px; margin-right: 20px; text-align: left;">
<button type="button" (click)="showCreateModal()" pButton icon="pi pi-check" label="Create Poll"></button>
</div>
<div *ngFor="let pollId of pollIds" style="display: inline-block;">
<app-poll [pollKey]="pollId" (editEvent)="editPoll($event)" (deleteEvent)="deletePoll($event)"></app-poll>
</div>
</div>
<div *ngIf="displayCreateModal">
<app-poll-form [type]="'create'" (closeEvent)="closeModal($event)"></app-poll-form>
</div>
<div *ngIf="displayEditModal">
<app-poll-form [type]="'edit'" (closeEvent)="closeModal($event)"></app-poll-form>
</div>
poll.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import * as Chart from 'chart.js';
import { Observable } from 'rxjs';
import { FirebaseService } from '../services/firebase.service';
import { first } from 'rxjs/operators';
import { CardModule } from 'primeng/card';
import { AngularFireAuth } from '#angular/fire/auth';
#Component({
selector: 'app-poll',
templateUrl: './poll.component.html',
styleUrls: ['./poll.component.scss']
})
export class PollComponent implements OnInit {
poll:any;
#Input()
pollKey: string;
#Output()
editEvent = new EventEmitter<string>();
constructor(private firebaseService: FirebaseService, private afAuth: AngularFireAuth) { }
ngOnInit() {
this.firebaseService.getPoll(this.pollKey).subscribe(pollDoc => {
if (!pollDoc.payload.exists) {
return;
}
const pollData:any = pollDoc.payload.data();
this.poll = {
id: pollDoc.payload.id,
helperText: pollData.helperText,
pollType: pollData.pollType,
scoringType: pollData.scoringType,
user: pollData.user
};
}
edit() {
this.editEvent.emit(this.poll);
}
}
poll-form.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { AutoCompleteModule } from 'primeng/autocomplete';
import { SelectButtonModule } from 'primeng/selectbutton';
import { FirebaseService } from '../services/firebase.service';
import nflPlayers from '../../assets/data/players-nfl.json';
import nflPollTypes from '../../assets/types/poll-types-nfl.json';
import nflScoringTypes from '../../assets/types/scoring-types-nfl.json';
#Component({
selector: 'app-poll-form',
templateUrl: './poll-form.component.html',
styleUrls: ['./poll-form.component.scss']
})
export class PollFormComponent implements OnInit {
title:string;
btnLabel:string;
selectedScoringType:any;
choices:any;
selectedPollType:any;
selectedPollKey:any;
displayEditModal:boolean = false;
displayCreateModal:boolean = false;
filteredPlayersMultiple: any[];
displayModal:boolean = true;
nflPlayers:any = nflPlayers.Players;
nflPollTypes:any = nflPollTypes.types;
nflScoringTypes:any = nflScoringTypes.types;
activePlayers:any;
#Input()
type: string;
#Output()
closeEvent = new EventEmitter<string>();
constructor(
private firebaseService: FirebaseService
) { }
ngOnInit() {
this.initFormDefaults();
}
initFormDefaults() {
this.choices = [[],[]];
this.selectedScoringType = this.nflScoringTypes[0];
this.selectedPollType = this.nflPollTypes[0];
if (this.type == "create") {
this.btnLabel = "Create";
this.title = "Create Poll";
} else {
this.btnLabel = "Update";
this.title = "Edit Poll";
}
// Filter out active NFL players
this.activePlayers = this.nflPlayers.filter(player => player.active == "1");
}
editPoll(poll) {
this.type = "edit"
this.activePlayers = this.nflPlayers.filter(player => player.active == "1");
this.selectedPollKey = poll.id;
// Set scoring type
this.selectedScoringType = this.nflScoringTypes.find((type) => {
return type.code == poll.scoringType
});
// Set poll type
this.selectedPollType = this.nflPollTypes.find((type) => {
return type.code == poll.pollType
});
// Populate edit modal with properly formatted Player objects
for (let i=0; i < poll.choices.length; i++) {
this.choices[i] = poll.choices[i].players.map((choicePlayer:any) => {
return this.activePlayers.find(player => player.playerId == choicePlayer.id);
});
}
}
}
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { first } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { PollFormComponent } from '../poll-form/poll-form.component';
#Component({
providers: [PollFormComponent],
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
displayEditModal:boolean = false;
constructor(
private pollFormComponent: PollFormComponent
) { }
ngOnInit() {
}
editPoll(poll) {
this.displayEditModal = true;
this.pollFormComponent.editPoll(poll);
}
}

Initial counter value not displaying on ChangeDetectionPush strategy

I am writing a simple counter. It has start,stop, toggle functionality in parent (app) and displaying changed value in child (counter) component using ChangeDetectionStrategy.OnPush.
Issue I am facing is not able to display initial counter value in child component on load.
Below are screenshot and code.
app.component.ts
import { Component } from '#angular/core';
import {BehaviorSubject} from 'rxjs';
#Component({
selector: 'app-root',
template: `<h1>Change Detection</h1>
<button (click)="start()">Start</button>
<button (click)="stop()">Stop</button>
<button (click)="toggleCD()">Toggle CD</button>
<hr>
<counter [data]="data$" [notifier]="notifier$"></counter>`,
})
export class AppComponent {
_counter = 0;
_interval;
_cdEnabled = false;
data$ = new BehaviorSubject({counter: 0});
notifier$ = new BehaviorSubject(false);
start() {
if (!this._interval) {
this._interval = setInterval((() => {
this.data$.next({counter: ++this._counter});
}), 10);
}
}
stop() {
clearInterval(this._interval);
this._interval = null;
}
toggleCD(){
this._cdEnabled = !this._cdEnabled;
this.notifier$.next(this._cdEnabled);
}
}
counter.component.ts
import {Component, Input, ChangeDetectionStrategy, OnInit, ChangeDetectorRef} from '#angular/core';
import {Observable} from 'rxjs/index';
#Component({
selector: 'counter',
template: `Items: {{_data.counter}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
#Input() data: Observable<any>;
#Input() notifier: Observable<boolean>;
_data: any;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.data.subscribe((value) => {
/**
Below this._data.counter is showing 0 in console.log but
not in template
**/
this._data = value;
this.cd.markForCheck();
});
this.cd.detach();
this.notifier.subscribe((value) => {
if (value) {
this.cd.reattach();
} else {
this.cd.detach();
}
});
}
}
I'm using Angular 6.1.0
your AppComponent data$ is a BehaviorSubject, which you have given an initial value. your CounterComponent data expects an Observable, which you subscribe to. The defaulted BehaviorSubject does not fire until it changes. to get the value you have to query it upon load:
#Input() data: BehaviorSubject<any>;
ngOnInit() {
this._data = this.data.value; // get the initial value from the subject
this.data.subscribe((value) => {
this._data = value;
this.cd.markForCheck();
}
);
should do the trick.

Using Tippy.js within an Angular component [duplicate]

I have a directive with the following code
import { Directive, Input, OnInit, ElementRef, SimpleChanges, OnChanges } from '#angular/core';
import tippy from 'tippy.js';
#Directive({
selector: '[tippy]'
})
export class TippyDirective implements OnInit, OnChanges {
#Input('tippyOptions') public tippyOptions: Object;
private el: any;
private tippy: any = null;
private popper: any = null;
constructor(el: ElementRef) {
this.el = el;
}
public ngOnInit() {
this.loadTippy();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes.tippyOptions) {
this.tippyOptions = changes.tippyOptions.currentValue;
this.loadTippy();
}
}
public tippyClose() {
this.loadTippy();
}
private loadTippy() {
setTimeout(() => {
let el = this.el.nativeElement;
let tippyOptions = this.tippyOptions || {};
if (this.tippy) {
this.tippy.destroyAll(this.popper);
}
this.tippy = tippy(el, tippyOptions, true);
this.popper = this.tippy.getPopperElement(el);
});
}
}
And using the directive as follows
<input tippy [tippyOptions]="{
arrow: true,
createPopperInstanceOnInit: true
}" class="search-input" type="text"
(keyup)="searchInputKeyDown($event)">
How can I have the Tippy shown on mouseenter or focus as these are the default triggers, from the tippy instance I have in the directive, this is what I get when I put console.log(this.tippy) on line 44
{
destroyAll:ƒ destroyAll()
options:{placement: "top", livePlacement: true, trigger: "mouseenter focus", animation: "shift-away", html: false, …}
selector:input.search-input
tooltips:[]
}
As I am getting an error when I try to use
this.popper = this.tippy.getPopperElement(el);
ERROR TypeError: _this.tippy.getPopperElement is not a function
How can I get this directive to work as I took it from a repo in github
https://github.com/tdanielcox/ngx-tippy/blob/master/lib/tippy.directive.ts
What is it that I am missing here, any help is appreciated, thanks
I'm not sure what they were trying to accomplish in the linked repo you have included. To get tippy.js to work though, you should be able to change the directive to the following:
import { Directive, Input, OnInit, ElementRef } from '#angular/core';
import tippy from 'tippy.js';
#Directive({
/* tslint:disable-next-line */
selector: '[tippy]'
})
export class TippyDirective implements OnInit {
#Input('tippyOptions') public tippyOptions: Object;
constructor(private el: ElementRef) {
this.el = el;
}
public ngOnInit() {
tippy(this.el.nativeElement, this.tippyOptions || {}, true);
}
}
Working example repo
This works with tippy.js 6.x
#Directive({selector: '[tooltip],[tooltipOptions]'})
export class TooltipDirective implements OnDestroy, AfterViewInit, OnChanges {
constructor(private readonly el: ElementRef) {}
private instance: Instance<Props> = null;
#Input() tooltip: string;
#Input() tooltipOptions: Partial<Props>;
ngAfterViewInit() {
this.instance = tippy(this.el.nativeElement as Element, {});
this.updateProps({
...(this.tooltipOptions ?? {}),
content: this.tooltip,
});
}
ngOnDestroy() {
this.instance?.destroy();
this.instance = null;
}
ngOnChanges(changes: SimpleChanges) {
let props = {
...(this.tooltipOptions ?? {}),
content: this.tooltip,
};
if (changes.tooltipOptions) {
props = {...(changes.tooltipOptions.currentValue ?? {}), content: this.tooltip};
}
if (changes.tooltip) {
props.content = changes.tooltip.currentValue;
}
this.updateProps(props);
}
private updateProps(props: Partial<Props>) {
if (this.instance && !jsonEqual<any>(props, this.instance.props)) {
this.instance.setProps(this.normalizeOptions(props));
if (!props.content) {
this.instance.disable();
} else {
this.instance.enable();
}
}
}
private normalizeOptions = (props: Partial<Props>): Partial<Props> => ({
...(props || {}),
duration: props?.duration ?? [50, 50],
});
}
Using this looks like:
<button [tooltip]="'Hello!'">Hover here</button>
<button [tooltip]="'Hi!'" [tooltipOptions]="{placement: 'left'}">Hover here</button>
You can also use the lifecyle hook ngAfterViewInit then you don't need the setTimeout.
public ngAfterViewInit() {
this.loadTippy();
}

Angular 4 - Share data with directive attribute

I'm trying to make a tooltip directive/component, but everything i tried, i cannot use interpolation in my tooltip to use variables from a repeat.
My home markup looks like this:
<md-card class='col-md-3 image-gallery' *ngFor="let advertiser of AdvertiserService.advertisers;let i = index" [#fadeIn]>
<md-card-content
[tooltip]="template" [advertiser]="advertiser">
//some other markup
</md-card-content>
</md-card>
My tooltip directive looks like this:
import { ComponentFactoryResolver, ComponentRef, Directive, ElementRef,
HostListener, Injector, Output, Input, ReflectiveInjector, Renderer2,
TemplateRef, Type, ViewContainerRef, ViewRef } from '#angular/core';
import { TooltipComponent } from './tooltip.component';
import { AdvertiserClass } from './../advertiser/advertiser-class';
#Directive({
selector: '[tooltip]'
})
export class TooltipDirective {
// We can pass string, template or component
#Input('tooltip') content: string | TemplateRef<any> | Type<any>;
#Input('advertiser') advertiser: AdvertiserClass;
private componentRef: ComponentRef<TooltipComponent>;
constructor(private element: ElementRef,
private renderer: Renderer2,
private injector: Injector,
private resolver: ComponentFactoryResolver,
private vcr: ViewContainerRef) {
}
#HostListener('mouseenter')
mouseenter() {
//console.log(this.advertiser);
if (this.componentRef) return;
const factory =
this.resolver.resolveComponentFactory(TooltipComponent);
const injector = ReflectiveInjector.resolveAndCreate([
{
provide: 'tooltipConfig',
useValue: {
host: this.element.nativeElement
}
}
]);
this.componentRef = this.vcr.createComponent(factory, 0, injector,
this.generateNgContent());
}
generateNgContent() {
if (typeof this.content === 'string') {
const element = this.renderer.createText(this.content);
return [[element]];
}
if (this.content instanceof TemplateRef) {
const viewRef = this.content.createEmbeddedView({});
return [viewRef.rootNodes];
}
// Else it's a component
const factory = this.resolver.resolveComponentFactory(this.content);
const viewRef = factory.create(this.injector);
return [[viewRef.location.nativeElement]];
}
#HostListener('mouseout')
mouseout() {
this.destroy();
}
destroy() {
this.componentRef && this.componentRef.destroy();
this.componentRef = null;
}
ngOnDestroy() {
this.destroy();
}
}
And my tooltip component looks like this:
import { Component, Directive, ElementRef, Inject, OnInit, ViewChild, Input
} from '#angular/core';
import { AdvertiserClass } from './../advertiser/advertiser-class';
#Directive({
selector: '.tooltip-container'
})
export class TooltipContainerDirective {
}
#Component({
template: `
<div class="tooltip-container" [ngStyle]="{top: top}">
{{advertiser | json}}
</div>
`,
styles: [
`
.tooltip-container {
background-color: black;
color: #fff;
display: inline-block;
padding: 0.5em;
position: absolute;
}
`
]
})
export class TooltipComponent implements OnInit {
#Input('advertiser') advertiser: AdvertiserClass;
top: string;
#ViewChild(TooltipContainerDirective, { read: ElementRef }) private
tooltipContainer;
constructor( #Inject('tooltipConfig') private config) {
}
ngOnInit() {
const { top } = this.config.host.getBoundingClientRect();
const { height } =
this.tooltipContainer.nativeElement.getBoundingClientRect();
this.top = `${top - height}px`;
}
}
How could i use the {{advertisers}} interpotalion in the code that would works?
I have tried every variant of this, but i couldnt make pass the repeated data to the tooltip components template.
As is, your tooltip directive knows about the advertiser, but the TooltipComponent, who's data is used to generate the view, does not. What you need to do is pass the advertiser from the directive to the TooltipComponent when the directive creates it. I would probably do that in the 'tooltipconfig' object that you're creating and injecting into the TooltipComponent.
const injector = ReflectiveInjector.resolveAndCreate([
{
provide: 'tooltipConfig',
useValue: {
host: this.element.nativeElement,
advertiser: this.advertiser
}
}
]);
Then in the ToolTipComponent you can pull that value out of the config object in the constructor to make it available to the template
constructor( #Inject('tooltipConfig') private config) {
this.advertiser = config.advertiser;
}
or you could make your config object public in the constructor and bind to {{config.advertiser}} in the template.

Categories