I am having a bit of a problem with getting the following situation done in Angular.
I am using Angular 4 and this is the situation.
app.component.html contains a wrapper div which I would like to be able to change it's color by adding a class to it.
My problem is that I have different layers of components.
For example, if I wanted to change the class in app.component.html it would have this:
app.component - root
button-wrapper.component - holds the button
button.component - message comes from there.
Normally I could do this:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';
myClass = '';
handleEvent(value) {
console.log(value);
myClass = value;
}
}
and in app.component.html have this:
<div [ngClass]="myClass">
<app-button (outputEvent)="handleEvent($event)"></app-button>
</div>
BUT, my problem is that app-button component is inside button-wrapper component so it looks like this:
<div [ngClass]="myClass">
<button-wrapper></div>
</div>
So where would I put this:
(outputEvent)="handleEvent($event)"
How can I go round this issue?
You can chain events as long as their direct children:
button.component.ts (selector: app-button)
startChainEvent() {
this.outputEvent.emit('className');
}
button-wrapper.component.html
<app-button (outputEvent)="handleEvent($event)"></app-button>
button-wrapper.component.ts (selector: app-button-wrapper)
handleEvent(e) {
this.outputEvent.emit(e);
}
app.component.html
<app-button-wrapper (outputEvent)="handleEvent($event)"></app-button-wrapper>
app.component.ts
handleEvent(e) {
myClass = e;
}
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.
I'm building a simple web based application using angular version 6.
In my application there is a component which has a child component. There is a function in this component(In the parent component, not the child component.) and I want to invoke that function using a button which is in the child component.
This image explains the format of my components.
I think its regarding to angular #Output. But i can't manage it.
This is how my code has organized.
Parent Component - component.ts file
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-teacher-home',
templateUrl: './teacher-home.component.html',
styleUrls: ['./teacher-home.component.scss']
})
export class TeacherHomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
formView: boolean = false;
toggleForm(){
this.formView = !this.formView;
}
}
Parent component - component.html file
<div>
<child-compnent></child-compnent>
</div>
Child component - component.html file
<div>
<button>Toggle Form view</button>
</div>
i want to callthe function toggleForm() of parent component when the button clicked which is in child component.
read this article: Understanding #Output and EventEmitter in Angular
child component:
#Component({
selector: 'app-child',
template: `<button (click)="sendToParent('hi')" >sendToParent</button> `
})
export class AppChildComponent {
#Output() childToParent = new EventEmitter;
sendToParent(name){
this.childToParent.emit(name);
}
}
parent component:
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
toggle(){
console.log('toggle')
}
}
parent html:
<app-child (childToParent)="toggle($event)"></app-child>
working DEMO
You have a couple of ways to do this :
Is to create an event inside the child component and then give it a callback, something like this:
#Output('eventName') buttonPressed = new EventEmitter();
and call buttonPressed.emit() when you want the event to be triggered
on the parent side it will look like this :
<div>
<child-compnent (eventName)="toggleForm()"></child-compnent>
</div>
Another way is to create a shared service that will contain the shared functions and data for both components
You need to use #Output decorator inside your child component and emit an event when the button present clicked inside your child.
For eg: -
Child component.html
<div>
<button (click)="childButtonClicked()">Toggle Form view</button>
</div>
Child component.ts
export class ChildComponent {
#Output triggerToggle: EventEmitter<any> = new EventEmitter<any>();
...
childButtonClicked() {
this.triggerToggle.emit(true);
}
...
}
Parent Component
<div>
<child-compnent (triggerToggle)="toggleForm()"></child-compnent>
</div>
You can use EventEmitter of angular to listen to events from your child component.
parent.component.ts
toggleForm($event) {}
parent.component.html
<div>
<child-compnent (trigger)="toggleForm($event)" ></child-compnent>
</div>
child.component.ts
#Output() trigger : EventEmitter<any> = new EventEmitter<any>();
buttonClick(){
this.trigger.emit('click');
}
I'm new to Angular.
I'm trying to use xterm.js (https://xtermjs.org/) but it display badly.
Here is the render :
Render
I created a xterm component. The xterm.component.ts file code is :
import { Component, OnInit } from '#angular/core';
import { Terminal } from "xterm";
#Component({
selector: 'app-xterm',
templateUrl: './xterm.component.html',
styleUrls: ['./xterm.component.css'],
})
export class XtermComponent implements OnInit {
public term: Terminal;
container: HTMLElement;
constructor() { }
ngOnInit() {
this.term = new Terminal();
this.container = document.getElementById('terminal');
this.term.open(this.container);
this.term.writeln('Welcome to xterm.js');
}
}
My xterm.component.html only contains this :
<div id="terminal"></div>
I don't really know what to do more ...
Thanks in advance guys
You must set the component encapsulation
#Component({
encapsulation: ViewEncapsulation.None,
...
})
Encountered the same problem and found this page.
Maybe this is too late for the OP, but could be useful for others.
The styles are wrong because 1) the xterm.css is not loaded, and 2) the encapsulation.
My solution to 1) was to add #import 'xterm/dist/xterm.css'; in the scss file for this component.
And 2) can be solved by setting encapsulation: ViewEncapsulation.None as Victor96's answer, or better setting encapsulation: ViewEncapsulation.ShadowDom.
Hope this helps.
I know this is old, but I had to put terminal initialation in ngAfterViewInit. Otherwise the DOM elements are undefined.
Try to use in template reference variable by using the hash symbol
<div #myTerminal></div>
and in component
#ViewChild('myTerminal') terminalDiv: ElementRef;
In ngOnInit
ngOnInit() {
this.term = new Terminal();
this.term.open(this.terminalDiv.nativeElement);
this.term.writeln('Welcome to xterm.js');
}
I'm working on small Angular 5 project, and I have a simple component which is representing an food product and it looks like this:
[![enter image description here][1]][1]
This component is nested component, it is part of my main component.
When I click on this component, quantity component/modal is shown:
<app-product-item (onInfoEdit)="InfoModal.show($event)"></app-product-item>
But now I would like to show another modal if some condition is satisfied, for example
If condition is satisfied then show this quantity modal (which is component itself) otherwise show some another modal (which is another component), so:
<app-product-item "if something is satisfied than this code on the right is ok otherwise lets display another modal with different method" (onInfoEdit)="InfoModal.show($event) (onSomethingElse)="AnotherModal.show($event)></app-product-item>
So I'm not sure if this is even possible because I need to show 2 different modals ( different components ) on same component's click, so basically if product has some property defined than show quantity info, otherwise show product info, and quantity info and product info are separated components..
Thanks guys
Cheers
You would be looking at something like this:
<app-product-item #productItem
(click)="productItem?.product?.property ? InfoModal.show($event) : AnotherModal.show($event)"
(onInfoEdit)="InfoModal.show($event)"
(onSomethingElse)="AnotherModal.show($event)">
</app-product-item>
where "property" would be a property of "product", which would be an object defined inside your Product Item Component.
Please also note that the conditional statements inside templates are not recommended and should be carried to the component.
You can do it by creating component which will conditionally render one of two components. One condition will load info component and other condition will load quantity component. Let's call this conditional modal something like: food-modal, inside food-modal.component.html template the code could look like:
<h1>CONDITIONAL MODAL CONTAINER</h1>
<ng-container *ngIf="product.name === 'Chicken'">
<app-info [product]="product"></app-info>
</ng-container>
<ng-container *ngIf="product.name === 'Cow'">
<app-quantity [product]="product"></app-quantity>
</ng-container>
And inside food-modal.component.ts the code could look like:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-food-modal',
templateUrl: './food-modal.component.html',
styleUrls: ['./food-modal.component.css']
})
export class FoodModalComponent implements OnInit {
#Input()
public product: Product;
constructor() {}
ngOnInit() {}
}
All you have to do now is call this component where you want it and pass in Product model. For sake of demonstration I'll put everything inside app component in order to load food-modal. The app.component.html could look like:
<app-food-modal [product]="chickenProduct">
</app-food-modal>
And inside app.component.ts code could be like this:
import { Component } from '#angular/core';
import { Product } from './models/Product.model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public chickenProduct: Product;
constructor() {
this.chickenProduct = {
id: 1,
quantity: 2,
name: 'Chicken'
};
}
}
Now app component will pass Product object named chickenProduct to food-modal component and bind it to modal's product property. After that the conditional rendering will happen.
Rest of the code could look like:
info.component.html:
<p>
This modal is for INFO. Product name is: {{ product.name }}
</p>
info.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-info',
templateUrl: './info.component.html',
styleUrls: ['./info.component.css']
})
export class InfoComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
quantity.component.html:
<p>
This modal is for QUANTITY. Product name is: {{ product.name }}
</p>
quantity.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-quantity',
templateUrl: './quantity.component.html',
styleUrls: ['./quantity.component.css']
})
export class QuantityComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
product.model.ts:
export interface Product {
id: number;
quantity: number;
name: string;
}
I've implemented solution like this and everything worked fine! Change name property of product to Cow and conditional fire will happen and load quantity component, take it back to Chicken and conditional fire will load info component.
I assume you just wanted to know how to trigger conditional firing, so that's why I've done this by hard coding string, checking whether name of product is Chicken or Cow.
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>