I have a tooltip component that I'd like to accept an input with an element reference but I amn not sure how to wire it up.
import {AfterViewInit, Component, Input, ViewChild} from '#angular/core';
#Component({
selector: 'tooltip',
templateUrl: 'tooltip.html'
})
export class TooltipComponent {
#Input() el: string;
target_el: any;
constructor() {
// do something with `el`
}
}
// View
<tooltip [el]="'#a'">Hello</tooltip>
<button #a>I'm a button</button
I think I have to use #ViewChild but I'm not sure how to implement with a variable.
You can use [style.top.xp]='top', Just pass a number you want as an input, or calculate it somehow
Related
How can I access a string of text given within the tags of a component
<my-custom-component>THIS TEXT</my-custom-component>
Within a template, I can use ng-content, or if it is an instance of some other class I can access it within the component definition like demonstrated in these examples. However I am interested in detecting if there is a string of text there or not, which I believe would make providedText undefined. However, I am always getting undefined.
#ContentChild(Element, { static: true }) providedText: Text | undefined;
I have tried Text as the first element passed to #ContentChild. Passing any will not work (I don't know why).
StackBlitz
I am interested mostly in finding if there is a string or undefined, but am also curious why ContentChild(Text... isn't working.
Edit:
I have added a potential solution, but it seems pretty imperfect, so I hope something better comes along.
Edit 2:
I now understand that #ContentChild is not a mechanism for selecting whatever native HTML I want without wiring it up to Angular’s dependency graph with a ref, directive, etc.
I am still curious if my proposed solution below is a bad idea for any reason.
My solution for now (since I wish to capture all transcluded content) is to wrap ng-content in a containing element, then get its innerText.
#Component({
selector: "app-parent",
template: `
<span #transcludedContainerRef>
<ng-content></ng-content>
</span>
`
})
export class ParentComponent implements AfterViewInit {
#ViewChild("transcludedContainerRef", { static: false })
transcludedContainerRef: ElementRef | undefined;
buttonText: string;
ngAfterViewInit() {
const isButtonTextPresent = this.transcludedContainerRef.nativeElement
.innerText;
if (isButtonTextPresent) {
console.log(isButtonTextPresent); // successfully logs content
}else {
console.log('No text set');
}
}
}
It does feel hacky, but it works. I am holding out for something better.
it's difficult if I don't know about your <my-custom-component>
In general if your custom component it's only
<ng-content></ng-content>
You can inject in constructor the elementRef
constructor(public el:ElementRef){}
From a parent
<hello >
Start editing to see some magic happen :)
</hello>
You can use
#ViewChild(HelloComponent,{static:false}) helloComponent:HelloComponent
click()
{
console.log(this.helloComponent.el.nativeElement.innerHTML)
}
If your component has any variable -or ViewContent-, you can access this variables in a similar way
So the other way to read the inner text from the component is that child component emit the value whatever it get's as input from other component. See below:
hello.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit {
#Input() name: string;
#Output() innerText: EventEmitter<string> = new EventEmitter();
ngOnInit() {
this.innerText.emit(this.name);
}
}
app.component.ts
import { Component, ContentChild, AfterContentInit, OnInit } from "#angular/core";
#Component({
selector: "app-parent",
template: "content from <code>app-parent</code>"
})
export class ParentComponent implements AfterContentInit {
#ContentChild(Element, { static: true }) providedText: Text | undefined;
ngAfterContentInit() {
console.log("ngAfterContentInit Content text: ", this.providedText);
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
_innerText: string;
ngAfterContentInit() {}
get childContent(): string {
return this._innerText;
}
set childContent(text) {
this._innerText = text;
}
innerTextFn(innertext: string) {
this.childContent = innertext;
console.log('Event: ', innertext);
}
}
app.component.html
<hello name="{{ name }}" (innerText)="innerTextFn($event)"></hello>
<app-parent>This is the content text</app-parent>
Here is stackblitz url to check: https://stackblitz.com/edit/angular-bacizp
I hope this may helpful for you and if yes then accept this as correct answer.
Trying to do child to parent communication with #Output event emitter but is no working
here is the child component
import { Component, OnInit, Output, Input, EventEmitter } from '#angular/core';
#Component({
selector: 'app-emiter',
templateUrl: './emiter.component.html',
styleUrls: ['./emiter.component.css']
})
export class EmiterComponent implements OnInit {
#Output() emitor: EventEmitter<any>
constructor() { this.emitor = new EventEmitter()}
touchHere(){this.emitor.emit('Should Work');
console.log('<><><><>',this.emitor) // this comes empty
}
ngOnInit() {
}
}
this is the html template
<p>
<button (click)=" touchHere()" class="btn btn-success btn-block">touch</button>
</p>
The console.log inside the touchHere it shows nothing
even if I put this inside the parent component it show nothing as well
parent component
import { Component , OnInit} from '#angular/core';
// service I use for other stuff//
import { SenderService } from './sender.service';
// I dont know if I have to import this but did it just in case
import { EmiterComponent } from './emiter/emiter.component'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
user: any;
touchThis(message: string) {
console.log('Not working: ${message}');
}
constructor(private mySessionService: SenderService) { }
}
and here is the html template
<div>
<app-emiter>(touchHere)='touchThis($event)'</app-emiter>
</div>
Parent component template:
<app-emitor (emitor)='touchThis($event)'></app-emiter>
In parent template #Output should be 'called', not the child method.
Also, see: https://angular.io/guide/component-interaction#parent-listens-for-child-event
Here’s an example of how we write a component that has outputs:
#Component({
selector: 'single-component',
template: `<button (click)="liked()">Like it?</button>`
})
class SingleComponent {
#Output() putRingOnIt: EventEmitter<string>;
constructor() {
this.putRingOnIt = new EventEmitter();
}
liked(): void {
this.putRingOnIt.emit("oh oh oh");
}
}
Notice that we did all three steps: 1. specified outputs, 2. created an EventEmitter that we attached
to the output property putRingOnIt and 3. Emitted an event when liked is called.
If we wanted to use this output in a parent component we could do something like this:
#Component({
selector: 'club',
template: `
<div>
<single-component
(putRingOnIt)="ringWasPlaced($event)"
></single-component>
</div>`
})
class ClubComponent {
ringWasPlaced(message: string) { console.log(`Put your hands up: ${message}`);
} }
// logged -> "Put your hands up: oh oh oh"
Again, notice that:
putRingOnIt comes from the outputs of SingleComponent
ringWasPlaced is a function on the ClubComponent
$event contains the thing that wasemitted, in this case a string
<app-emiter (emitor)="touchThis($event)" ></app-emiter>
By using #Output() you should apply the event you need to emit in the directive of the emitter component.Adding the name of the variable to the the directive and but the emitted over function inside the quotation passing the $event.
touchHere() is the method from which you are binding some value to emit with your EventEmitter. And your EventEmitter is 'emitor'.
So your code will work if you simply do the below:
<app-emiter (emitor)='touchThis($event)'></app-emiter>
Plnkr:
https://plnkr.co/edit/ka6ewGPo1tgnOjRzYr3R?p=preview
I have a subscription that will change overtime:
ngOnInit(){
this.route.snapshot.data.remote.subscribe(x => {
this.obs$ = x[0];
});
}
I have a template work-post.component.html that showcases these changes:
<h1>{{obs$.title}}</h1>
<p>
{{obs$.paragraph}}
</p>
Question:
When obs$ gets each next/new value, Is it possible to animate the enter and leave animations of these values. Can obs$.title obs$.paragraph bindings have a "crossfade" e.g. fade out old text, fade in the new text on change? if so, how could I implement this animation inside of my component and template:
component:
import { Component, ChangeDetectionStrategy, Input, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'work-post',
templateUrl: 'work-post.component.html',
styleUrls: ['work-post.component.scss'],
})
export class WorkPostComponent implements OnInit{
public obs$;
constructor(private route: ActivatedRoute){}
ngOnInit(){
this.route.snapshot.data.remote.subscribe(x => {
this.obs$ = x[0];
});
}
}
Here's a video of how the UI currently looks.
If I understood well, you want to apply X method (animations) when your obs$ changes value. So, in my opinion, you need a listener.
For this, I would go for DoCheck.
In component:
import { Component, DoCheck, ElementRef, OnInit } from '#angular/core';
export class MyClass implements OnInit, DoCheck {
ngDoCheck() {
if(this.obs$ !== 'whateverValue') {
// Apply an animation because this.obs$ changed
}
}
Documentation: https://angular.io/docs/ts/latest/api/core/index/DoCheck-class.html
Update 1:
I suggest that you store an initial version of this.obs$ in, for example, this.obs_initial.
And inside the logic in the listener you compare both, if this.obs$ is different from its previous value (this.obs_initial) then means that this.obs$ changed and therefore we trigger X animation.
On my parent page I have a link here:
<a (click)="showPermissionsRates(5757);">Link</a>
The function sets it:
showPermissionsRates(item) {
this.currentEventPoolId = item;
}
With a child component on the parent page here:
<app-event-pools-permissions-rates [eventPoolId]="currentEventPoolId "></app-event-pools-permissions-rates>
And then in my child component TS file I use:
inputs: ['eventPoolId']
But how do I get that value of '5757' in the child component? Such as using alert?
You should be able to just use #Input() on the child property.
I've put this together showing a VERY basic example, but without more to go on regarding your issues, it's hard to know what you need:
https://plnkr.co/edit/y9clOla1WrPFmhMJoz7o?p=preview
The gist is to use #Input() to mark your inputs in the child component, and map those in the template of the parent.
import {Component} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import { ChildComponent } from 'child.component.ts';
#Component({
selector: 'my-app',
template: `
<div>
<button (click)="changeProperty('ABC 123')">Click Me!</button>
<child-component [childProperty]="parentProperty"></child-component>
</div>
`,
})
export class App {
public parentProperty: string = "parentProp";
public changeProperty(newProperty: string) : void {
this.parentProperty = newProperty;
}
}
Then, in the child:
import {Component, Input} from '#angular/core'
#Component({
selector: 'child-component',
template: `
<div>Hello World: {{ childProperty }}</div>
`,
})
export class ChildComponent {
#Input()
childProperty:string;
constructor() {
this.childProperty = 'childProp'
}
}
I think you are setting value to at input variable in a click event, then you have to listen for it in the child component constructor using ngonchanges
ngOnChanges(changes: SimpleChanges) {
if(changes['eventpoolid'] && changes['eventpoolid'].currentValue) {
// you get updated value here
}
}
I want to show a popover as the user clicks on the input field which works fine but I want the data-content attribute of that popover be coming from the template of a child component. Here is an example:
parent.ts
import {Component,AfterViewInit} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChildComponent} from './child_test.ts';
#Component({
selector: 'app',
template: `<input type='text' data-toggle="popover" data-trigger="focus" data-placement="bottom" [attr.data-content]="getPopoverContent()" />`,
providers: [ChildComponent]
})
class AppComponent implements AfterViewInit{
constructor(private _child: ChildComponent) {}
getPopoverContent(){
return this._child; //returning empty object instead of child template
}
ngAfterViewInit(){
$("input").popover();
}
}
bootstrap(AppComponent);
child.ts
import {Component} from 'angular2/core';
#Component({
selector: "child-component",
template: "<div>Popover content from child.</div>"
})
export class ChildComponent{};
Should I use DynamicComponentLoader instead of dependency injection? if so then how can I achieve this?
Here's a workaround:
Assign the a temporary variable to the component you want to display
<transaction-filter #popoverComponent></transaction-filter>
Important: The component above must expose an ElementRef in its definition
constructor(public el: ElementRef) {}
Create the element that will show the popover
<button class="btn-sm btn-link text-muted"
data-animation="true"
data-placement="bottom"
title="Create Rule"
[popover]="popoverComponent">
Create Rule...
</button>
Now the popover directive itself:
/// <reference path="../../typings/tsd.d.ts"/>
import 'bootstrap'
import { Directive, ElementRef, Input} from 'angular2/core'
declare var $: JQueryStatic;
#Directive({
selector: '[popover]',
})
export class PopoverDirective {
#Input('popover') _component: any
_popover: JQuery
constructor(private _el: ElementRef) { }
ngOnInit() {
// Hide the component
this._component.el.nativeElement.style.display = "none"
// Attach it to the content option
this._popover = $(this._el.nativeElement)
.popover({
html: true,
content: this._component.el.nativeElement
})
// When the below event fires, the component will be made visible and will remain
this._popover.on('shown.bs.popover', () => {
this._component.el.nativeElement.style.display = "block"
})
}
}
One problem is that binding to an attribute stringifies the value
[attr.data-content]
therefore this approach won't work.
It seems the Bootstrap popover expects a string, therefore this would be fine but stringifying an Angular component won't you give it's HTML.