Pass element to parent component after output child component - javascript

I just output something from a child component and I was wondering how I can pass along an element from the parent template to the parent component after this.
Normally I make a variable of the element (#textArea in this case), but somehow I can't pass it along. Does someone know how this is done?
Parent template: editReview($event, textArea) is the function that gets sent from the child component. I receive the $event part in my editReview fucntion, but not the element reference.
<app-review-list [movie]="movie" (editReview)="editReview($event, textArea)"></app-review-list>
<hr>
<form ngNativeValidate (ngSubmit)="onSubmit(f)" #f="ngForm">
<div class="form-group">
<label for="review">Write A Review:</label>
<textarea
#textArea
(keyup.enter)="onSubmit(f)"
rows="4"
cols="50"
type="text"
id="review"
ngModel
class="form-control"
name="review"
required
></textarea>
</div>
<button type="button" class="btn btn-danger" (click)="onClear(f)">Clear</button>
<button class="btn btn-primary" type="submit">Save</button>
</form>
This is the parent component. In the editReview method, console.log(content) gives me the result I desired, but the other console.log gives me nothing
import {Component, ElementRef, Input, OnInit} from "#angular/core";
import {NgForm} from "#angular/forms";
import {ReviewService} from "./review.service";
import {ActivatedRoute} from "#angular/router";
#Component({
selector: 'app-review',
templateUrl: './review.component.html',
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit{
movieId;
#Input('movie') movie;
constructor(private reviewService: ReviewService, private route: ActivatedRoute){}
editReview(content, elem){
console.log(content);
console.log(elem)
}
}

There is an issue with passing a temple reference variable into a method if that template reference variable is set on an element that is anywhere within a structural directive (such as an *ngIf).
If in your actual code (not the posted snippet) you have a structural directive, that explains the issue. If you don't, it is not clear why it is not working.
In either case, another way to do it is with the ViewChild decorator.
Parent component
In the parent component, add the following declaration:
#ViewChild('textArea') myElementRef: ElementRef;
And modify the editReview method as follows:
editReview(content){
console.log(content);
console.log(this.myElementRef);
}

Related

How do I not render ViewChild element but access it in the parent component instead?

I have a component that I'm using like this in a different component:
<inline-help>
<help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
<help-body i18n="lang.helpBody"></help-body>
</inline-help>
In inline-help I'm using #ViewChild and ng-content to display the help-title and help-body.
#Component({
selector: 'inline-help',
template: `
<div class="inline-help">
<div class="help-icon">
<i class="en-icon-help"></i>
</div>
<div #helptext class="inline-help-text text-left">
<ng-content select="help-title"></ng-content>
<ng-content select="help-body"></ng-content>
</div>
</div>`
})
export class InlineHelpComponent implements AfterViewInit {
#Input() fixed: boolean;
#ViewChild('helptext', {static: false}) text: ElementRef;
......
What I want to do is create a new component like <inline-help-content> that I can use as such inside :
#Component({
selector: 'inline-help',
template: `
<div class="inline-help">
<div class="help-icon">
<i class="en-icon-help"></i>
</div>
<inline-help-content [helpTitle]="something" [helpBody]="something"></inline-help-content>
</div>`
})
But I DONT want to change all the instances of
<inline-help>
<help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
<help-body i18n="lang.helpBody"></help-body>
</inline-help>
that I use in other parts of the codebase, since that's a lot. Is it possible to parse the ViewChild and then get the text inside it and call another component to with the texts as inputs?
In your InlineHelpComponent template, you can wrap the <ng-content>s in elements with template reference variables assigned to them:
<div #helpTitle><ng-content select="help-title"></ng-content></div>
<div #helpBody><ng-content select="help-body"></ng-content></div>
Those template reference variables give you access to the native elements, so you can then pass the innerText properties of those elements into your new InlineHelpContentComponent (just be sure to hide the projected content in InlineHelpComponent so it doesn't display twice). Here's what the InlineHelpComponent template would look like:
<div class="inline-help">
<div style="display:none;">
<div #helpTitle><ng-content select="help-title"></ng-content></div>
<div #helpBody><ng-content select="help-body"></ng-content></div>
</div>
<inline-help-content
*ngIf="showInlineHelpContent"
[helpTitle]="helpTitle.innerText"
[helpBody]="helpBody.innerText">
</inline-help-content>
</div>
Here's a StackBlitz demonstrating this approach.
I found I had to include that *ngIf="showInlineHelpContent" on the inline-help-content component, and set the flag in ngAfterContentInit after a setTimeout, otherwise I was getting an ExpressionChangedAfterItHasBeenCheckedError error.

How to open/show my component on a HTML button click - Angular

I'm making simple application, there are 3 components right now, one Parent component, called MAIN COMPONENT, and two child components, one of the child component is displaying selected vehicles and that works fine, but after selection is done I need to click on add button in my app, to open modal (which is another component) where I should choose a customer/client so I could mark that vehicle's are selected customers ownership.
This is how it looks in general:
So basically when add button is clicked (that add button is part of toolbox component) there should be shown modal (another component which holds customers) where I could choose a client.
Component that should shown when add is pressed is: customer.component.html
Now I will post my code:
1.) posting component that should hold clients customer.component.html
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Clients</h4>
</div>
<div class="modal-body item-edit-container">
<h4 class="modal-title" style="text-align: center;">Find client by ID</h4>
<div class="form-group">
<input type="text" class="form-control dash-form-control" id="" placeholder="" autofocus>
<div style="margin-top: 10px;">
<select class="form-control dash-form-control select2" style="width: 100%;">
<option selected disabled>Search for client..</option>
<option>Person 1</option>
<option>Person 2</option>
<option>Person 3</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn save-keyboard-btn" data-dismiss="modal"></button>
</div>
</div>
#Component({
selector: 'app-customer',
templateUrl: './customer.component.html',
styleUrls: ['./customer.component.css']
})
export class ClientComponent implements OnInit {
ngOnInit() {
}
}
I general I've no idea how this should be done, maybe I should place something like this on my main component :
<app-customer></app-customer> and somehow show it when I click on button add, but I really don't know how to achieve this? Could anyone write me some simple steps which I might follow or whatever...
Thanks guys
Cheers
So please keep in mind my question at the end is simple, I need to show a modal which represeting my component when button is clicked..
Thanks
Cheers
as you said it's simple, in the main component for example you add this:
import {Component, OnInit} from'#angular/core';
export class MainComponent implements OnInit{
public shoud_open = false;
openChildComponent(){
this.should_open = true;
}
itemSelected(item){
console.log(item);
}
}
in the html of the MainComponent you add:
<button (click)="openChildComponent()">Open</button>
<div *ngIf="shouldOpen">
<child-component (onItemSelect)="itemSelected($event)" [items]="persons"></child-component>
</div>
now because you are using bootstrap modal, you need to open it programmatically, i assume that you already using JQuery and it's defined somewhere, so in the ChildComponent you do something like this:
import {Component, AfterViewInit, OutPut, Input, EventEmitter} from'#angular/core';
export class ChildComponent implements AfterViewInit{
#OutPut()
public onItemSelect: EventEmitter<any>;
#Input()
public items: Array<any>;
constructor(){
this.onItemSelect= new EventEmitter<any>();
}
ngAfterViewInit(){
$('#modal_id').modal('show');
}
selectItem(item){
this.onItemSelect.emit(item);
}
}
in the html code of ChildComponent you add this:
<select multiple name="items" (change)="selectItem($event)">
<option *ngFor="let item of items" [value]="item">Item</option>
</select>
i didn't test this code, but i'm just giving you example of how you communicate with components, conditionally display them

How to focus a div block on click of button using Angular with TypeScript

I have a button and another div with some content. I want to set focus to the div when user clicks on the button. I know in javascript i can do it by document.getElementById("divID").focus();But is there any way in typescript.
You can use #ViewChild
#ViewChild('selector') selector: ELementRef;
focus() {
this.selector.nativeElement.focus()
}
in html
<button (click)='focus()'> focus</button>
and the 2 approach will be
<sole-element #el> <sole-element>
<button (click)='focus(el)'> focus</button>
or
<button (click)='el.focus()'> focus</button>
and in ts file
focus(el) {
el.nativeElement.focus()
}
Try this :
in html file :
<div>
<button class="btn btn-primay" (click)="onFocus()">onFocus</button>
</div>
<div>
<input type="text" name="name" #focusContent>
</div>
component.ts
import { Component, ElementRef, ViewChild } from '#angular/core';
export class AppComponent {
#ViewChild('focusContent') el: any;
onFocus() {
this.el.nativeElement.focus();
}
}

Angular2 detect presence of ng-content target [duplicate]

Suppose I have a component:
#Component({
selector: 'MyContainer',
template: `
<div class="container">
<!-- some html skipped -->
<ng-content></ng-content>
<span *ngIf="????">Display this if ng-content is empty!</span>
<!-- some html skipped -->
</div>`
})
export class MyContainer {
}
Now, I would like to display some default content if <ng-content> for this component is empty. Is there an easy way to do this without accessing the DOM directly?
Wrap ng-content in an HTML element like a div to get a local reference to it, then bind the ngIf expression to ref.children.length == 0:
template: `<div #ref><ng-content></ng-content></div>
<span *ngIf=" ! ref.children.length">
Display this if ng-content is empty!
</span>`
Updated for Angular 12; old logic ("ref.nativeElement.childNodes.length") gives error, as nativeElement is undefined nowadays.
EDIT 17.03.2020
Pure CSS (2 solutions)
Provides default content if nothing is projected into ng-content.
Possible selectors:
:only-child selector. See this post here: :only-child Selector
This one require less code / markup. Support since IE 9: Can I Use :only-child
:empty selector. Just read further.
Support from IE 9 and partially since IE 7/8: https://caniuse.com/#feat=css-sel3
HTML
<div class="wrapper">
<ng-content select="my-component"></ng-content>
</div>
<div class="default">
This shows something default.
</div>
CSS
.wrapper:not(:empty) + .default {
display: none;
}
In case it's not working
Be aware of, that having at least one whitespace is considered to not beeing empty.
Angular removes whitespace, but just in case if it is not:
<div class="wrapper"><!--
--><ng-content select="my-component"></ng-content><!--
--></div>
or
<div class="wrapper"><ng-content select="my-component"></ng-content></div>
There some missing in #pixelbits answer. We need to check not only children property, because any line breaks or spaces in parent template will cause children element with blank text\linebreaks.
Better to check .innerHTML and .trim() it.
Working example:
<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
Content if empty
</span>
When you inject the content add a reference variable:
<div #content>Some Content</div>
and in your component class get a reference to the injected content with #ContentChild()
#ContentChild('content') content: ElementRef;
so in your component template you can check if the content variable has a value
<div>
<ng-content></ng-content>
<span *ngIf="!content">
Display this if ng-content is empty!
</span>
</div>
If you want to display a default content why dont you just use the 'only-child' selector from css.
https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
for eg:
HTML
<div>
<ng-content></ng-content>
<div class="default-content">I am default</div>
</div>
css
.default-content:not(:only-child) {
display: none;
}
Inject elementRef: ElementRef and check if elementRef.nativeElement has any children. This might only work with encapsulation: ViewEncapsulation.Native.
Wrap the <ng-content> tag and check if it has children. This doesn't work with encapsulation: ViewEncapsulation.Native.
<div #contentWrapper>
<ng-content></ng-content>
</div>
and check if it has any children
#ViewChild('contentWrapper') contentWrapper;
ngAfterViewInit() {
contentWrapper.nativeElement.childNodes...
}
(not tested)
Sep 2021
There is another technique to accomplish the default content if not provided from the implementation component by using *ngTemplateOutlet directive which allows us to have the customization more control:
Example in source component:
import { Component, ContentChild, TemplateRef } from '#angular/core';
#Component({
selector: 'feature-component',
templateUrl: './feature-component.component.html',
})
export class FeatureComponent {
#ContentChild('customTemplate') customTemplate: TemplateRef<any>;
}
Then in HTML template:
<ng-container
[ngTemplateOutlet]="customTemplate || defaultTemplate"
></ng-container>
<ng-template #defaultTemplate>
<div class="default">
Default content...
</div>
</ng-template>
target component:
<!-- default content -->
<feature-component></feature-component>
<!-- dynamic content -->
<feature-component>
<ng-template #customTemplate>
<div> Custom group items. </div>
</ng-template>
</feature-component>
In my case I have to hide parent of empty ng-content:
<span class="ml-1 wrapper">
<ng-content>
</ng-content>
</span>
Simple css works:
.wrapper {
display: inline-block;
&:empty {
display: none;
}
}
With Angular 10, it has changed slightly. You would use:
<div #ref><ng-content></ng-content></div>
<span *ngIf="ref.children.length == 0">
Display this if ng-content is empty!
</span>
I've implemented a solution by using #ContentChildren decorator, that is somehow similar to #Lerner's answer.
According to docs, this decorator:
Get the QueryList of elements or directives from the content DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.
So the necessary code in the parent component will be:
<app-my-component>
<div #myComponentContent>
This is my component content
</div>
</app-my-component>
In the component class:
#ContentChildren('myComponentContent') content: QueryList<ElementRef>;
Then, in component template:
<div class="container">
<ng-content></ng-content>
<span *ngIf="*ngIf="!content.length""><em>Display this if ng-content is empty!</em></span>
</div>
Full example: https://stackblitz.com/edit/angular-jjjdqb
I've found this solution implemented in angular components, for matSuffix, in the form-field component.
In the situation when the content of the component is injected later on, after the app is initialised, we can also use a reactive implementation, by subscribing to the changes event of the QueryList:
export class MyComponentComponent implements AfterContentInit, OnDestroy {
private _subscription: Subscription;
public hasContent: boolean;
#ContentChildren('myComponentContent') content: QueryList<ElementRef>;
constructor() {}
ngAfterContentInit(): void {
this.hasContent = (this.content.length > 0);
this._subscription = this.content.changes.subscribe(() => {
// do something when content updates
//
this.hasContent = (this.content.length > 0);
});
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
}
Full example: https://stackblitz.com/edit/angular-essvnq
<ng-content #ref></ng-content> shows error "ref" is not declared.
The following is working in Angular 11 (Probably 10 also):
<div #ref><ng-content></ng-content></div>
<ng-container *ngIf="!ref.hasChildNodes()">
Default Content
</ng-container>
In Angular 12, the console reports the following for me:
Property 'nativeElement' does not exist on type 'HTMLElement'
There seems to exist a specific attribute childElementCount which you can use for this case.
As a consequence, I used this successfully, which does not wrap the dynamic content into additional elements/tags:
<div class="container">
<!-- some html skipped -->
<ng-container #someContent>
<ng-content></ng-content>
</ng-container>
<span
*ngIf="
someContent.childElementCount === undefined ||
someContent.childElementCount === 0
"
>
Display this if ng-content is empty!
</span>
<!-- some html skipped -->
</div>
in angular 11 I use this and works fine.
template file :
<h3 class="card-label" #titleBlock>
<ng-content select="[title]" ></ng-content>
</h3>
component:
#ViewChild('titleBlock') titleBlock: ElementRef;
hasTitle: boolean;
ngAfterViewInit(): void {
if (this.titleBlock && this.titleBlock.nativeElement.innerHTML.trim().length > 0)
{
this.hasTitle= true;
}
else
{
this.hasTitle= false;
}
}
This solution has worked for me (on angular version 12.0.2).
Note that this will probably not work if your content is dynamic and changes from empty to non-empty (or the other way) after the component was already loaded.
That can be fixed by adding code that changes hasContent inside ngOnChanges.
Example:
import {Component, ViewChild, AfterViewInit} from '#angular/core';
#Component({
selector: 'my-component',
template: '<div [ngClass]="[hasContent ? 'has-content' : 'no-content']">
<span #contentRef>
<ng-content></ng-content>
</span>
</div>']
})
export class Momponent implements AfterViewInit {
#ViewChild('contentRef', {static: false}) contentRef;
hasContent: boolean;
ngAfterViewInit(): void {
setTimeout(() => {
this.hasContent = this.contentRef?.nativeElement?.childNodes?.length > 1;
});
}
}

Angular 2 - Add CSS class from Component event

I'm using Angular 2 with TypeScript and MDL and have the following simple component. Basically it's an input with the button, and then a collection of spans underneath it. So you enter some "tag", then click the button, and the collection is updated.
One issue is when I add the item to collection like this:
this.chips.push(this.chip);
this.chip = '';
Then the input's value becomes blank (as wanted), but the "is-dirty" CSS MDL class on the parent div is not removed. So what I want to do is to simply remove the class from DIV element in Component's template.
How do I query DOM in Angular2 and manipulate it (add/remove CSS classes)?
I was trying to play with Renderer, but no luck so far.
Note: I can't bind this "is-dirty" class because MDL javascript updates it manually, when text is being entered in the input.
The code:
import {Component, View, Directive, Input, ElementRef, Renderer} from 'angular2/core'
import {NgIf, NgFor} from 'angular2/common'
#Component({
selector: 'chipCollection'
})
#View({
directives: [NgIf, NgFor],
template: `
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" id="chip" [(ngModel)]="chip" (keyup.enter)="addChip()">
<label class="mdl-textfield__label" for="chip">{{ label }}</label>
<label class="mdl-button mdl-js-button mdl-button--icon aa-mdl-input-button">
<i class="material-icons" (click)="addChip()">add</i>
</label>
</div>
<div *ngIf="chips.length > 0">
<span *ngFor="#chip of chips" class="aa-chip">{{ chip }}</span>
</div>
`
})
export class ChipCollection {
#Input() chips: Array<string> = ["chip1", "chip2"];
#Input() label: string;
chip: string;
private renderer: Renderer;
private element: ElementRef;
constructor(renderer: Renderer, element: ElementRef){
this.renderer = renderer;
this.element = element;
}
addChip() {
if(this.chip) {
this.chips.push(this.chip);
this.chip = '';
debugger; //testing here...
// what I need to do here is to find the DIV element and remove its "is-dirty" CSS class, any ideas?
this.renderer.setElementClass(this.element.nativeElement, 'is-dirty', false);
}
}
}
EDIT:
Here are two "dirty" classes. I guess ng-dirty is added by ngModel. However I need to also remove parent's "is-dirty". I think it's MDL class.
See screenshot:
what I need to do here is to find the DIV element and remove its "is-dirty" CSS class
Don't do that on element. That class is set by ngModel and you should use ngModel's API to set its .dirty state to false.
More
http://blog.ng-book.com/the-ultimate-guide-to-forms-in-angular-2/
Yeah, the is-dirty is set by mdl, not by ngModel (as suggested by the other answer). I don't know why this didn't come up when I tried to solve the opposite issue a few months ago: Register model changes to MDL input elements, but binding to the class list, https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngClass, might be a better way:
for your example:
<div class="mdl-textfield mdl-js-textfield mdl-text-field--floating-label"
[class.is-dirty]="false">
or for mdl-tabs:
<div class="mdl-tabs__panel"
[class.is-active]="someIsActiveBoolean">
or for mdl-checkboxes:
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect"
[class.is-checked]="val == 1">

Categories