I have a component that all it does is render , its something like this:
#Component({
selector: 'my-comp',
host: ???,
template: `
<ng-content></ng-content>
`
})
export default class MyComp {
#Input() title: string;
public isChanged: boolean;
}
The component has a isChanged property and I want to apply styling on the host element based on that isChanged property. Is this even possible?
You use the class and style prefix for this. Here is a sample:
#Component({
selector: 'my-comp',
host: {
'[class.className]': 'isChanged'
},
template: `
<ng-content></ng-content>
`
})
export default class MyComp {
#Input() title: string;
public isChanged: boolean;
}
See the Günter's answer for more details:
ngClass in host property of component decorator does not work
Solution using #HostBinder
The accepted solution is using the host metadata property which goes against the rules of TSLint:
TSLint: Use #HostBinding or #HostListener rather than the host
metadata property (https://angular.io/styleguide#style-06-03)
The same can be achieved using #HostBinding instead:
import { Component, HostBinding, Input } from '#angular/core';
#Component({
selector: 'my-comp',
template: `
<ng-content></ng-content>
`
})
export default class MyComp {
#Input() title: string;
public isChanged: boolean;
#HostBinding('class.className') get className() { return this.isChanged; }
}
Not sure what you're trying to do but something like this should suffice where you use ngAfterViewInit and ElementRef:
import {AfterViewInit, ElementRef} from '#angular/core';
#Component({
selector: 'my-comp',
host: ???,
template: `
<ng-content></ng-content>
`
})
export default class MyComp implements AfterViewInit {
#Input() title: string;
public isChanged: boolean;
constructor(private _ref: ElementRef) {}
ngAfterViewInit() {
var host = this._ref.nativeElement;
if (this.isChanged) {
host.style.width = '200px';
}
}
}
If you want to do some checking for isChanged every time it changes you could implement ngDoCheck instead/as well:
ngDoCheck() {
if (this.isChanged !== this.previousIsChanged) {
var host = this._ref.nativeElement;
if (this.isChanged) {
host.style.width = '200px';
}
}
}
I think you want to let your component fire an event that can be catched by the host (and possibly pass some data with it).
To do that you would have an #output property like:
#Output() isChanged: EventEmitter<any> = new EventEmitter()
then in your code you could do:
this.isChanged.emit(some value to pass)
And catch it like:
(isChanged)="doSomething($event)"
Related
My Problem: I created a CardComponent acts like a card view with css and html and has a constructor.
I want to use it in a cards array (of its type). I use service to store the cards' data.
home comp. is using the service and loop over with ngFor, This is the code.. and below is the error I get...
Is there another way of using this so it will work?
card.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent{
imageSrc : string;
title : string;
constructor(imgSrc : string, title : string) {
this.imageSrc = imgSrc;
this.title = title;
}
ngOnInit(): void {
}
cards.service.ts:
import { CardComponent } from './card/card.component';
export class CardsService{
ctgryCards : CardComponent[] = [
new CardComponent("https://cdn3.iconfinder.com/data/icons/outline-amenities-icon-set/64/Beauty_Saloon-512.png", "Beauty"),
new CardComponent("https://www.pinclipart.com/picdir/middle/391-3917890_get-business-value-from-sustainable-data-electronics-icon.png", "Electronics")
];
getAllCtgryCards(){
return this.ctgryCards.slice();
}
}
home.component.ts:
import { Component, OnInit } from '#angular/core';
import { CardComponent } from '../card/card.component';
import { CardsService } from '../cards.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
categoryCards : CardComponent[] = [];
constructor(private cardsServ : CardsService) { }
ngOnInit(): void {
this.categoryCards = this.cardsServ.getAllCtgryCards();
}
}
home.component.html:
<app-card *ngFor = "let card of categoryCards"></app-card>
Error NG2003:
ERROR in src/app/card/card.component.ts:12:15 - error NG2003: No
suitable injection token for parameter 'imgSrc' of class
'CardComponent'. Found string
constructor(imgSrc : string, title : string) {
In card.component.ts:
Update the constructor to be
//import { Inject } from '#angular/core';
constructor(#Inject(String) private imgSrc : string, title : string)
OR
//import { Inject } from '#angular/core';
constructor(#Inject('imgSrc') private imgSrc : string, title : string)
According to https://angular-2-training-book.rangle.io/di/angular2/inject_and_injectable ; #Inject() is a manual mechanism for letting Angular know that a parameter must be injected. #Inject decorator is only needed for injecting primitives.
The primitive types are number, string, boolean, bigint, symbol, null, undefined.
I have 2 components
Parent component is
#Component({
selector: 'parent',
template: `
<child [obj]="obj"> </child>
`,
styleUrls: [''],
})
export class parentComponent implements OnInit{
obj = {
id:1;
name:'abc'
}
}
and child component is
#Component({
selector: 'child',
templateUrl: '',
styleUrls: [''],
})
export class ChildComponetimplements OnInit{
#Input() obj : any;
}
If I change any of the property in the obj in the parent, it is not getting updated in the child component.
Maybe because the obj reference is not changed.
Please suggest me the solution for this.
You have to use ngOnChanges like below
Parent Component
export class AppComponent {
obj = {
id:1,
name:'abc'
}
name = 'Angular';
changeObject() {
this.obj.id++;
}
}
Parent Template
<button (click)="changeObject()">Change Object</button>
<hello name="{{ name }}" [obj]="obj"></hello>
Child Component
import { Component, Input, OnInit, OnChanges } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1><p>{{ obj | json }}</p>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit, OnChanges {
#Input() name: string;
#Input() obj;
ngOnInit() {
}
ngOnChanges(changes) {
this.obj = changes.currentValue ? changes.currentValue.obj : this.obj;
}
}
Working example is here in Stackblitz
Stackblitz Demo
obj = {
id: 1,
name: 'Hello'
}
changeObject() {
this.obj.name = 'Hello change';
}
Use ngOnChanges to listen to changes of input properties. You will pass the object from parent to child and whenever there is any change in the object that you have passed as input to the child will give SimpleChanges object in ngOnChanges hook of child component.
interface OnChanges {
ngOnChanges(changes: SimpleChanges): void
}
Example
#Component({selector: 'my-cmp', template: `...`})
class MyComponent implements OnChanges {
// TODO(issue/24571): remove '!'.
#Input()
prop !: number;
ngOnChanges(changes: SimpleChanges) {
// changes.prop contains the old and the new value...
}
}
For more on ngOnChanges
I'm new in angular2. my code is like this:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'main',
template: `
<div class="current">
</div>
`
})
export class MainComponent implements OnInit {
ngOnInit(): void {
//change the div class from current to next...
}
}
i'd like to change the div class from 'current' to 'next'.
i appropriate if you let me know what is the best way do that?
One option is to use a template reference variable.
In the example below, the reference variable #target is added to the desired element and then the decorator #ViewChild (#ViewChild('target') target) allows you to access the variable in your component.
From there, you can get a reference to the DOM element by accessing the nativeElement property on the variable.
Here is an example where the class name is updated:
import { Component, AfterViewInit, ViewChild } from '#angular/core';
#Component({
selector: 'main',
template: `
<div #target class="current">
</div>
`
})
export class MainComponent implements AfterViewInit {
#ViewChild('target') target;
constructor() { }
ngAfterViewInit(): void {
let element = this.target.nativeElement;
element.className = 'next';
}
}
However, it's worth pointing out that you can handle most DOM manipulation with the build-in DOM directives. In this case you could just use the ngClass directive to bind a variable with the class attribute:
import { Component, AfterViewInit } from '#angular/core';
#Component({
selector: 'main',
template: `
<div [ngClass]="targetClass">
</div>
`
})
export class MainComponent implements AfterViewInit {
private targetClass: string = 'current';
constructor() { }
ngAfterViewInit(): void {
this.targetClass = 'next';
}
}
In Angular 4 to dynamically create a component you can use ngComponentOutlet directive: https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html
something like this:
Dynamic component
#Component({
selector: 'dynamic-component',
template: `
Dynamic component
`
})
export class DynamicComponent {
#Input() info: any;
}
App
#Component({
selector: 'my-app',
template: `
App<br>
<ng-container *ngComponentOutlet="component"></ng-container>
`
})
export class AppComponent {
this.component=DynamicComponent;
}
How do I pass #Input() info: any; information in this template <ng-container *ngComponentOutlet="component"></ng-container> ?
Such a feature was discussed in the pull request for ngComponentOutlet but was dropped for now.
Even the componentRef shown currently in https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html is not public and therefore not available https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L78
I'd suggest you create your own directive derived from https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L72
and assign values to inputs like shown in Angular 2 dynamic tabs with user-click chosen components
this.compRef.instance.someProperty = 'someValue';
With the help of the post of #Günter Zöchbauer I solved a similar problem this way - I hope you can adapt it somehow.
First I defined some interfaces:
// all dynamically loaded components should implement this guy
export interface IDynamicComponent { Context: object; }
// data from parent to dynLoadedComponent
export interface IDynamicComponentData {
component: any;
context?: object;
caller?: any;
}
then I implemented them inside of the dynamically loaded component
dynamicLoadedComponentA.ts
// ...
export class DynamicLoadedComponentA implements IDynamicComponent {
// ...
// data from parent
public Context: object;
// ...
After that I built a new component which is responsible for the magic. Important here is that I had to register all dyn. loaded components as entryComponents.
dynamic.component.ts
#Component({
selector: 'ngc-dynamic-component',
template: ´<ng-template #dynamicContainer></ng-template>´,
entryComponents: [ DynamicLoadedComponentA ]
})
export class DynamicComponent implements OnInit, OnDestroy, OnChanges {
#ViewChild('dynamicContainer', { read: ViewContainerRef }) public dynamicContainer: ViewContainerRef;
#Input() public componentData: IDynamicComponentData;
private componentRef: ComponentRef<any>;
private componentInstance: IDynamicComponent;
constructor(private resolver: ComponentFactoryResolver) { }
public ngOnInit() {
this.createComponent();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['componentData']) {
this.createComponent();
}
}
public ngOnDestroy() {
if (this.componentInstance) {
this.componentInstance = null;
}
if (this.componentRef) {
this.componentRef.destroy();
}
}
private createComponent() {
this.dynamicContainer.clear();
if (this.componentData && this.componentData.component) {
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(this.componentData.component);
this.componentRef = this.dynamicContainer.createComponent(factory);
this.componentInstance = this.componentRef.instance as IDynamicComponent;
// fill context data
Object.assign(this.componentInstance.Context, this.componentData.context || {});
// register output events
// this.componentRef.instance.outputTrigger.subscribe(event => console.log(event));
}
}
}
here the usage of this shiny new stuff:
app.html
<!-- [...] -->
<div>
<ngc-dynamic-component [componentData]="_settingsData"></ngc-dynamic-component>
</div>
<!-- [...] -->
app.ts
// ...
private _settingsData: IDynamicComponent = {
component: DynamicLoadedComponentA,
context: { SomeValue: 42 },
caller: this
};
// ...
I think for now you can use
https://www.npmjs.com/package/ng-dynamic-component
It is made specifically for this issue
So, What I am trying to do seems like it would be trivial. And it probably is. But I can't figure it out. My question is:How can I pass a variable from #Input to a service in an Angular2 component? (Code has been simplified)
My component is as follows:
import { Component, Input } from '#angular/core';
import { CMSService } from '../cms.service';
#Component({
selector: 'cmstext',
templateUrl: './cmstext.component.html',
styleUrls: ['./cmstext.component.css']
})
export class CMSTextComponent {
constructor(private cms: CMSService) { }
#Input() id : string;
content = this.cms.getContent(this.id); // this.id is NULL so content is NULL
}
And then my service:
import { Injectable } from '#angular/core';
#Injectable()
export class CMSService {
constructor() { }
getContent(textId:string) : string {
this.text = textId; // textId is NULL so this.text returns NULL
return this.text;
}
}
My component template:
<p>id: {{id}}</p>
<p>Content: {{content}}</p>
When <cmstext id="4"></cmstext> is added to another component template the output is:
id: 4
content:
I'm just diving into Angular2 any help or suggestions would be greatly appreciated!
Just make it a setter and put the code there:
#Input()
set id(value : string) {
this.content = this.cms.getContent(value);
}
As pointed out by #Kris Hollenbeck,ngOnInit() was the answer. My final code looked like this. The component now passed the variable to the service.
import { Component, Input, OnInit } from '#angular/core';
import { CMSService } from '../cms.service';
#Component({
selector: 'cmstext',
templateUrl: './cmstext.component.html',
styleUrls: ['./cmstext.component.css']
})
export class CMSTextComponent implements OnInit {
public content : string;
#Input() id : string;
constructor(private cms: CMSService) { }
ngOnInit() {
this.content = this.cms.getContent(this.id);
}
}
This assigned the data from the service to the variable "content" and the id passed from the element attribute to the variable "id". Both variables were then accessible to the template!