How does consumer supply component constructor parameters in Angular 2? - javascript

I want to modify the field of a component instance.
For example, in test.component.ts:
#Component({
selector: 'test',
})
export class TestComponent {
#Input() temp;
temp2;
constructor(arg) {
this.temp = arg;
this.temp2 = arg * 2;
}
}
I want to set the values of temp and temp2 using the constructor. I know one approach is to use input property by doing something like:
<test [temp]='1'></test>
However, this is done after the constructing time and temp2 won't change accordingly. How can I supply component constructor argument from a consumer's point of view, such that the value of "temp" and "temp2" are set at constructing time?
Thanks!

In fact inputs of a component are only available from the ngOnInit method because of the component lifecycle:
#Component({
selector: 'test',
})
export class TestComponent {
#Input() temp;
ngOnInit() {
console.log(this.temp);
}
}
Moreover we can only use parameters in the component constructor that are provided through dependency injection.
So you can't use the constructor for the temp property because the component lifecycle. Regarding it depends on how you make it available. If it's through dependency injection, it will work but you need to use the #Inject decorator to specify what to inject.
You could also have a look at this question for more details:
Difference between Constructor and ngOnInit

sharedServcie.ts
import {Injectable} from 'angular2/core';
#Injectable()
export class sharedService{
test:string="Angular2";
}
boot.ts
import {sharedService} from './sharedService';
...
...
bootstrap[App,[sharedService]]
import {sharedService} from './sharedService';
#Component({
selector: 'test',
})
export class TestComponent {
temp;
constructor(sharedService:sharedService) {
this.temp = sharedService.test;
console.log(this.temp) //Angular2
}
}

I think the answer Thierry Templier explains your problem, but
you say in a comment:
I updated the question, hope this can be more clear. By using input
property, I can only change temp, but temp2 will not update
accordingly.
I hope this is what you want to achieve and help you.
import {Input, Component} from 'angular2/core'
#Component({
selector: 'my-test',
template: `
<h1> arg value: {{ arg }} </h1>
<h1> temp value: {{ temp }} </h1>
<h1> temp1 value: {{ temp1 }} </h1>
`
})
export class test {
#Input() arg : number;
temp : number;
temp1: number;
constructor(){
}
ngOnInit(){
this.temp = this.arg;
this.temp1 = this.arg * 2;
}
}
#Component({
selector: 'my-app',
directives: [test],
template: `
<h2>Hello {{name}}</h2>
<my-test [arg]="1"></my-test>
`
})
export class App {
constructor() {
this.name = 'Angular2';
}
}
test Plunker

Related

How to pass data from child to parent?

I need to pass one variable, that is inside my child component, to parent page.
This variable that I am trying to pass, is the array result of Barcode Scanner.
And I need to pass it to parent to send to API.
childComponent.ts
this.consultList;
parentComponent.ts
export class ParentComponent implements OnInit {
#Input() consultList: any[] = [];
testCall() {
console.log('Test Consult: ', this.consultList;
}
Here is an example stackblitz project to test parent-child data transfer, using #Input() and #Output()mechanism
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'child',
template: `
<h1>Hello {{ name }}! This is child component</h1>
<button (click)="sendEventToParent()">Send data to parent</button>
`,
styles: [
`
h1 {
font-family: Lato;
}
`
]
})
export class ChildComponent {
#Input() name: string;
#Output() eventFromChild: EventEmitter<string> = new EventEmitter();
sendEventToParent(): void {
this.eventFromChild.emit('data from child');
}
}
here is the parent component html called child
<child name="{{ name }}" (eventFromChild)="onEvent($event)"></child>
<h1>This is parent component</h1>
<p>{{dataFromChild}}</p>
and event bindin like that
import { Component, VERSION } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular ' + VERSION.major;
dataFromChild = '';
onEvent(event): void {
this.dataFromChild = event;
}
}
What you are thinking of is called an abstract class. An abstract class can define abstract properties just like an interface, abstract methods just like an interface, and unlike an interface it can actually implement methods. You cannot initialize an abstract class, but you can inherit code for re-use from it.
https://codesandbox.io/s/patient-breeze-h4s3t?file=/src/index.ts
abstract class Parent {
abstract someProperty: string;
someCall() {
console.log(this.someProperty);
}
}
class ChildOne extends Parent {
someProperty = "I am child one";
}
class ChildTwo extends Parent {
someProperty = "I am child two";
}
const one = new ChildOne();
const two = new ChildTwo();
one.someCall(); // "I am child one";
two.someCall(); // "I am child two";

Access text (not instance of another component) with ContentChild

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.

Angular 2 - Get passed object to component via inputs

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
}
}

How is component template transformed into template element [duplicate]

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();
}
}
}

Injecting array of services

As far as I now, services need to be provided and injected, meaning each service has to be placed inside the constructor eg.:
constructor (private a: AService, private B: BService) {}
In my situation, I have a bunch of services (all implementing the same interface) I would like to hold in an array. How can I achieve this without a lot of redundancy (since I already have to state each twice for providing and injecting - right?)?
Regular classes instead of injectable services would not work for me because each of the services might need to use other services like HTTP which again have to be injected.
With a simple helper array, let's call it "registry" and a dedicated array provider, we can make all registered instances available. Here a quick example:
export class Examples extends Array<ExampleService> {}
const examplesRegistry = [ExampleServiceA, ExampleServiceB];
export function buildExampleProviders() {
return [
...examplesRegistry,
{
provide: Examples,
useFactory: (...args) => new Examples(...args),
deps: examplesRegistry,
},
];
}
As demonstrated below, we can still inject single instances (like protected exampleA: ExampleServiceA) or all of them with protected examples: Examples)
full example + usage
/// example.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export abstract class ExampleService {
someProperty = Math.random();
abstract name: string;
}
#Injectable()
export class ExampleServiceA extends ExampleService {
override name = 'a';
}
#Injectable()
export class ExampleServiceB extends ExampleService {
override name = 'b';
}
/// examples.ts
export class Examples extends Array<ExampleService> {}
const examplesRegistry = [ExampleServiceA, ExampleServiceB];
export function buildExampleProviders() {
return [
...examplesRegistry,
{
provide: Examples,
useFactory: (...args) => new Examples(...args),
deps: examplesRegistry,
},
];
}
/// app.component.ts
import { Component } from '#angular/core';
import { ExampleServiceA } from './example.service';
import { buildExampleProviders, Examples } from './examples';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [buildExampleProviders()],
})
export class AppComponent {
constructor(
protected examples: Examples,
protected exampleA: ExampleServiceA
) {}
}
<!-- app.component.html -->
<pre>
<ng-container *ngFor="let ex of examples; let i = index">
examples[{{i}}]:
.name: {{ ex.name }}
.someProperty: {{ ex.someProperty }}
.constructor.name: {{ ex.constructor.name }}
</ng-container>
exampleA:
.name: {{ exampleA.name }}
.someProperty: {{ exampleA.someProperty }}
.constructor.name: {{ exampleA.constructor.name }}
</pre>
You can also try it out here:
https://stackblitz.com/edit/angular-ivy-3kaagr?file=src/app/examples.ts

Categories