I'm currently facing an issue in updating a value in the DOM.
To explain it quickly and as simple as possible, I currently have two components (A and B). B inherits from A, uses A's view as template but also inject its template into A (ng-content) so B's view is like:
<A>
<template>
<div *ngFor="let item of items" (click)="selected($event, item)">
<span>{{item.name}}<span>
</div>
</template>
</A>
And A's view is like :
<ng-content select="template"></ng-content>
<div *ngIf="searching">Hey I'm looking for answer</div>
<div *ngIf="!searching">I already have my answer</div>
The component B has only one private variable which is the array of items and A's component has one specific method called by B which is :
private searching: boolean = true;
selected(event, value: any): void {
event.stopPropagation();
this.searching = false; // This.searching is updated in component but not in DOM
this.selectedItem = value; // BUT this.selectedItem is updated in component AND in DOM !!
}
Changing this.selectedItem works, but this.searching doesn't therefore only "Yes" is displayed since searching is always TRUE.
Here is the plunkr.
Can someone enlighten me please ? Thank you.
As per my comment, method in the parent class can be used in child class so it would work, unlike you coded.I don't know more about typescript extends and ng-content. you better need to refer extends ng-content functionality and scope of the <template-example> .
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
declare var searching: boolean
#Component({
selector: 'my-app',
template: `<ng-content select="template-example"></ng-content>`,
})
export class App {
private searching: boolean = true;
private selectedItem: any;
constructor(private zone:NgZone) {
}
selected(event, value: any) {
event.stopPropagation();
this.searching = !this.searching;
console.log(this.searching);
this.selectedItem = value;
}
}
#Component({
selector: 'app-inheritent',
template: `
<my-app>
<template-example>
<div *ngFor="let item of items" (click)="selected($event, item)">
<a>{{item.name}}</a>
</div>
<div *ngIf="!searching">
<span>Nope.</span>
</div>
<div *ngIf="searching">
<span>Yes</span>
</div>
</template-example>
</my-app>` ,
})
export class AppInheritent extends App {
private items: any = [
{name: "1"},
{name: "2"},
{name: "3"},
{name: "4"},
{name: "5"},
];
constructor() {
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, AppInheritent ],
bootstrap: [ AppInheritent ],
schemas: [ NO_ERRORS_SCHEMA ]
})
export class AppModule {}
For those who could be interested... I finally found the answer thanks to #k11k2 and #Hareesh (I finally understood your answer) who enlightened me but this isn't entirely because of the extends. -- thanks.
Extends only confused my thoughts from the beginning.
In my example, I instantiated the component A (my-app) in my component B (app-inheritent). Because of the extends, I thought the component A and B would share the same variables and the same context but they absolutely don't.
In calling component B, we already have one context and in extending this latter with A, the two components share the same structure.
BUT, B is itself calling A so we got another context. A has the same structure than B but they aren't related in any way.
When I called the selected function, B actually called its PARENT selected method and not the function of the instantiated A component !
So, as #Hareesh said it, we have now multiple components. The only way to change the value is to make them communicate between each other.
Related
Hope someone can enlighten me.
Problem
I need to get a reference to a directive placed inside an inner component.
I'm using #ViewChild targeting Directive class, with {static:true}since it doesnt have to wait for state changes and use it later on lifecicle when the user clicks a button.
#ViewChild(DirectiveClass, {static:true}) childRef : DirectiveClass;
Expected
To have directive reference in childRef instance variable when the event happens.
Actual
childRef is undefined
I did research for similar problems and all seemed to be because ref was inside a *ngIf and should be {static: false}; or because the ref was intended to be used before it was fetched(before ngAfterViewInit hook). This is not the case, this case is fair simplier and yet cant get whats wrong! :/
Reproduction
Situation
so, I got two components and a directive. Lets call them ParentComponent and SonComponent. Directive is applied in son component, so lets call it SonDirective.
Basically this is the structure.
<app-parent>
<app-son> </app-son> //<- Directive inside
</app-parent>
Code
//parent.component.ts
#Component({
selector: 'app-parent',
template: `
<div>
<h2> parent </h2>
<app-son></app-son>
</div>
`,
})
export class AppParentComponent implements AfterViewInit{
#ViewChild(AppSonDirective,{static: true}) childRef : AppSonDirective;
constructor() {}
ngAfterViewInit() {
console.log("childRef:",this.childRef)
}
}
// son.component.ts
#Component({
selector: 'app-son',
template: `
<div appSonDirective>
<p> son <p>
</div>
`,
})
export class AppSonComponent {
constructor() {}
}
//son.directive.ts
#Directive({
selector: '[appSonDirective]'
})
export class AppSonDirective {
constructor(){}
}
Note: if i move the view child ref to the son component it can be accessed. The issue seems to be at parent component (?)
Anyway... here is the reproducion code(it has some logs to know whats going on)
Any thoughts will be helpfull.
Thanks in advance! :)
Problem is that #ViewChild only works around the component DOM but don't have access to the DOM of its children components, that would break encapsulation between components. In this case appSonDirective is declared in AppSonComponent DOM but because you're trying to access it from AppParentComponent it returns undefined because AppParentComponent can't access AppSonComponent DOM. It could work if you had something like this:
<app-parent>
<app-son appSonDirective></app-son>
</app-parent>
One solution is to expose the directive as a property of you child component. Something like:
#Component({
selector: 'app-son',
template: `
<div appSonDirective>
<p> son <p>
</div>
`,
})
export class AppSonComponent {
#ViewChild(AppSonDirective,{static: true}) childRef : AppSonDirective;
constructor() {}
}
and then in AppParentComponent
#Component({
selector: 'app-parent',
template: `
<div>
<h2> parent </h2>
<app-son></app-son>
</div>
`,
})
export class AppParentComponent implements AfterViewInit{
#ViewChild(AppSonComponent,{static: true}) son : AppSonComponent;
constructor() {}
ngAfterViewInit() {
console.log("childRef:",this.son.childRef)
}
}
I have two components and would like to pass data from the 'child' component that happens when a user has clicked an image from within that component.
I have two components (posting & gifpicker - the child component)
The function applyGif() is the function in the child component that I am using to pass data across - I want to pass this data to the parent component.
Note - the components have some code not required for this aspect removed from view in this post for extra clarity.
The HTML Component below currently shows nothing in the selectedGif for some reason in the view
-- Posting Component (Parent Component) --
/** long list of imports here **/
#Component({
selector: 'app-posting',
templateUrl: './posting.component.html',
styleUrls: ['./posting.component.scss'],
providers: [ GifpickerService ],
})
export class PostingComponent implements OnInit{
public selectedGif: any = '';
#ViewChild(GifpickerComponent) gifpicker: GifpickerComponent;
ngOnInit(): void {
}
constructor(#Inject(DOCUMENT) private document: any,
public gifpickerService: GifpickerService,
) {}
}
-- GifPickerComponent (Child Component) --
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {GifpickerService} from "./gifpicker.service";
#Component({
selector: 'app-gifpicker',
templateUrl: './gifpicker.component.html',
styleUrls: ['./gifpicker.component.scss'],
providers: [ GifpickerService ],
})
export class GifpickerComponent implements OnInit {
public selectedGif: any = {};
constructor(private gifpickerService: GifpickerService) {}
ngOnInit() {
}
applyGif(gif): any {
// this is an json object I want to use/see in the Posting HTML Component
let gifMedia = gif.media[0];
}
}
-- Posting Component HTML (want data from the gifPickerComponent applyGif() shown here --
<div>{{ selectedGif }}</div>
Have you tried using #Output() to pass the information from child to parent after applyGif() method ends.
In your GifPickerComponent declare:
#Output() gifSelected: EventEmitter<any> = new EventEmitter<any>(); // or whatever type your are sending
Once the GIF is selected in applyGif()
applyGif(gif): any {
this.gifPickerVisible = false;
this.uploadedGif = true;
let gifMedia = gif.media[0]; // this is an json object I want to use/see in the Posting HTML Component
this.gifSelected.emit(gifMedia);
}
In the PostingComponent HTML template file where you are using app-gifpicker:
<app-gifpicker (gifSelected)="onGifSelected($event)"></app-gifpicker>
Create onGifSelected in your posting.component.ts file and handle the result:
public onGifSelected(gif: any) {
// Do whatever you need to do.
this.selectedGif = gif;
}
In addition, your posting component is the parent and it hosts other components like your GIFPickerComponent, there is no need to provide the service in both components. It is enough to do it in the parent and it will be passed down to the child component. In other words, the same instance will be passed. With your current arrangement, both parent and child have two different instances of a service.
I want to access the text I have in a text area in my child component to put it on the parent component and keep it updated.
I was told that #input in angular 4 is supposed to perform two-way binding. But I can't do that that way, and I don't understand why.
I found a workaround for this issue. It includes an #Output to send the info to the parent component. But if Input already does that (in some way I don't know), I want to avoid it.
For example, this is my Parent Component
import { Component } from '#angular/core';
#Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
})
export class SettingsComponent {
private studyDesignText = 'Text';
constructor() {
}
public handleStudyDesignUpdated(designText: any) {
this.studyDesignText = designText;
}
}
It's html
<div class="section section-trials-settings-parent light rounded">
<div class="section section-trials-settings-child">
<div class="pure-g">
<div class="pure-u-1-1">
<app-settings-study-design
[studyDesignText]="studyDesignText">
</app-settings-study-design>
</div>
</div>
</div>
</div>
My child component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-settings-study-design',
templateUrl: './settings-study-design.component.html',
})
export class SettingsStudyDesignComponent implements OnInit {
#Input() studyDesignText: string;
constructor() {
}
ngOnInit() {
super.onInit();
loadControls();
}
loadControls(): void {
this.startAllTextAreas();
}
private startAllTextAreas() {
this.startTextArea('study-design-textarea');
}
private startTextArea(htmlId: string) {
// code to configure my text area; it's right...
}
If I change the value in the text area and send a signal with #Output so my parent component can be notified and console log the value, the printed value is the initial one. My friend did the same thing and it worked.
What am I missing?
#Input() is always one way binding from parent->child. Two way binding happens in this case, only when you have object as an input property. This is because, the reference for objects remain the same. And when one of the object updates, the other will also get updated. This is not true for string or number. It is always one way binding.
I know the textbook rules on that <div *ngFor="let foo of foobars">{{foo.stuff}}</div> turns into <template ngFor let-foo="$implicit" [ngForOf]="foobars"><div>...</div></template>. My question is two-fold:
HOW?
What do I need to do to leverage this mechanism ("microsyntax") myself?
Ie turn <div *myDirective="item">{{item.stuff}}</div> into <template myDirective let-item="$implicit"><div>{{item.stuff}}</div></template>?
Since I read ngFor's source code top to bottom, I can only assume this dark magic is in the compiler somewhere, I've been up and down the angular github, but I can't put my finger on it. Help!
Yes, all magic happens in the compiler.
Let's take this template:
<div *ngFor="let foo of foobars">{{foo}}</div>
First it will be transformed to the following:
<div template="ngFor let foo of foobars>{{foo}}</div>
And then:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
In Angular2 rc.4 it looks like this
First is generated ast tree node (Abstract Syntax Tree node) and then all magic happens in the TemplateParseVisitor.visitElement(https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L284) specifically at the bottom (https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/compiler/src/template_parser.ts#L394)
if (hasInlineTemplates) {
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectiveMetas = this._parseDirectives(this.selectorMatcher, templateCssSelector);
var templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
var templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst(
[], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts,
templateProviderContext.transformProviders,
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
element.sourceSpan);
}
return parsedElement;
This method returns EmbeddedTemplateAst. It's the same as:
<template ngFor let-foo [ngForOf]="foobars"><div>{{foo}}</div></template>
If you want to turn:
<div *myDirective="item">{{item.stuff}}</div>
into
<template myDirective let-item><div>{{item.stuff}}</div></template>
then you need to use the following syntax:
<div *myDirective="let item">{{item.stuff}}</div>
But in this case you won't pass context.
Your custom structural directive might look like this:
#Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(
private _viewContainer: ViewContainerRef,
private _templateRef: TemplateRef<any>) {}
#Input() set myDirective(prop: Object) {
this._viewContainer.clear();
this._viewContainer.createEmbeddedView(this._templateRef, prop); <== pass context
}
}
And you can use it like:
<div *myDirective="item">{{item.stuff}}</div>
||
\/
<div template="myDirective:item">{{item.stuff}}</div>
||
\/
<template [myDirective]="item">
<div>{{item.stuff}}</div>
</template>
I hope this will help you understand how structural directives work.
Update:
Let's see how it works (plunker)
*dir="let foo v foobars" => [dirV]="foobars"
So you can write the following directive:
#Directive({
selector: '[dir]'
})
export class MyDirective {
#Input()
dirV: any;
#Input()
dirK: any;
ngAfterViewInit() {
console.log(this.dirV, this.dirK);
}
}
#Component({
selector: 'my-app',
template: `<h1>Angular 2 Systemjs start</h1>
<div *dir="let foo v foobars k arr">{ foo }</div>
`,
directives: [MyDirective]
})
export class AppComponent {
foobars = [1, 2, 3];
arr = [3,4,5]
}
Here is the corresponding Plunker
See also
https://angular.io/docs/ts/latest/guide/structural-directives.html#!#the-asterisk-effect
https://teropa.info/blog/2016/03/06/writing-an-angular-2-template-directive.html
https://www.bennadel.com/blog/3076-creating-an-index-loop-structural-directive-in-angular-2-beta-14.htm
https://egghead.io/lessons/angular-2-write-a-structural-directive-in-angular-2
Live example you can find here https://alexzuza.github.io/enjoy-ng-parser/
*ngFor, *ngIf, ... are structural directives.
Either apply it on a <template> element or prefix it with a *
https://angular.io/docs/ts/latest/guide/structural-directives.html#!#unless
import { Directive, Input } from '#angular/core';
import { TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({ selector: '[myUnless]' })
export class UnlessDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) { }
#Input() set myUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
Hoping for some help on a more complex example that expands on the examples in angular's Tour of Heroes
Rather than submitting a single string each time, how would you submit multiple values like in the following example e.g.:
export class LittleTourComponent {
heroes = [ {
'name':'Hulk',
'power':'strength'
},{
'name':'Bulk',
'power':'appetite'
}];
I presume a new 'entry' made up of the submitted values should be pushed to the heroes array something like this:
addHero(newHero) {
if (newHero) {
var entry = {
'name': newHero.name,
'power': newHero.power
};
this.heroes.push(entry);
}
}
But what would be required in the template? Would you still use keyup.enter in this case?:
template:
<label>name</label
// how should the inputs be filled out in this scenario?
<input >
<label>power</label>
<input >
<button (click)=addHero(newHero)>Add</button>
<ul *ngFor="let hero of heroes">
<li>name:{{hero.name}}</li>
<li>power:{{hero.power}}</li>
</ul>
example also on plnkr
Any help appreciated. thanks!
Try and do this in your ts file:
import {Component} from '#angular/core';
class Hero {
name: string;
power: string;
}
export class LittleTourComponent {
newHero: Hero;
constructor() {
this.newHero = new Hero();
}
heroes = [{
'name': 'Hulk',
'power': 'strength'
}, {
'name': 'Bulk',
'power': 'appetite'
}];
addHero() {
if (this.newHero) {
var entry = {
'name': this.newHero.name,
'power': this.newHero.power
};
this.heroes.push(entry);
}
}
}
...and this in your html
<label>name</label>
<input [(ngModel)]="newHero.name">
<label >power</label>
<input [(ngModel)]="newHero.power">
<button (click)=addHero()>Add</button>
<ul *ngFor="let hero of heroes">
<li>name:{{hero.name}}</li>
<li>power:{{hero.power}}</li>
</ul>
your click listener is calling what it thinks is a reference to an element in the DOM which u havent defined nor would take paramaters. Trying putting quotes around that callback
<label>name</label
// how should the inputs be filled out in this scenario?
<input >
<label>power</label>
<input >
<button (click)="addHero(newHero)">Add</button>
<ul *ngFor="let hero of heroes">
<li>name:{{hero.name}}</li>
<li>power:{{hero.power}}</li>
</ul>
after further review, i notice ur referencing newHero in the little-tour component which does not exist in that components scope. Also, uve bound correctly to your inputs but i dont believe .value is the correct property to return the input... try
[(ngModel)]="input1"
in your class declaration ad
input1: String;
and then using that variable.
I didnt notice until right now that you arent importing your directive
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html'
})
export class AppComponent { }
since u are calling
<little-tour></little-tour>
in your app.component.html then this should be your app component
import { Component } from '#angular/core';
import {LittleTourComponent} from 'path-to-little-tour'
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [LittleTourComponent]
})
export class AppComponent { }