As title implies i'm looking for a way to bind an object with multiple properties to component #Inputs without having to explicitly write all of them
Let's say I have this object
let params = {
delta: 0.2,
theta: 2.3,
sigma: 'foo',
}
Instead of having to bind all of them individually like this
<my-component
[delta]="params.delta"
[theta]="params.theta"
[sigma]="params.sigma"/>
I would like bind all of them at once.
<my-component [someDirectiveIDontKnow]="params"/>
How can i do this?
Found a link to a previously asked question but couldn't get that to work properly.
Edit:
I'm not asking how to bind #Inputs. Imagine that the component I'm rendering has 40 #Inputs and I'm NOT allowed to rewrite it to just accept one #Input that could contain all the params.
So writing a template that uses this component gets really ugly and big.
....
<my-component
[param1]="params.param1"
[param2]="params.param2"
[param3]="params.param3"
[param4]="params.param4"
[param5]="params.param5"
[param6]="params.param6"
[param7]="params.param7"
[param8]="params.param8"
[param9]="params.param9"
[param10]="params.param10"
[param11]="params.param11"
[param12]="params.param12"
[param13]="params.param13"
[param14]="params.param14"
... and so on ....
/>
....
In my opinion, It would be best to define them all in a model
You would start with the following model
params.model.ts
import {SomeOtherModel} from './some-other.model'
export interface ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
}
Then in your component, you can force your input to take a specific model argument
my.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class MyComponent {
#Input() params: ParamsModel;
}
app.component.html
<my-component params="paramsModel"></my-component>
app.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class AppComponent implements OnInit {
paramsModel: ParamsModel;
ngOnInit(): void {
this.paramsModel = <ParamsModel>{someValue: someValue};
}
}
this way you have full code completion.
do note though! Angular does not deepwatch the contents, so changing the contents inside the Params object, will still have the same object ID in javascript, causing angular to not see the changes.
There are a few work-around for this
1: Bind every param (this is exactly what you do not want)
2: When changing contents of the model, destroy the instance and create a new instance everytime, you could do this by adding a constructor in the model and convert it olike this code
export class ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
constructor(config?: ParamsModel) {
Object.assign(this, config);
}
}
// first init
this.paramsModel = new ParamsModel(<ParamsModel>{someValue: someValue});
// updated init
this.paramsModel = new ParamsModel(this.paramsModel);
this.paramsModel.changedValue = changedValue; // (could also use an extend function on original params model)
3: Create an observer with events and trigger update events on the other side
4: use ngDoCheck to perform your own check if the contents changed
There is no generic directive to pass input properties in Angular. However, Angular supports binding any valid JavaScript type be it objects, arrays or primitives.
In the template
<my-component [params]="params"/>
In the class you have to use the #Input decorator to mark an object as an input. You can access it's value in any of the lifecycle hooks, some shown below. Note that params will not be set inside the constructor as view binding is performed after the class is instantiated.
class MyComponent {
#Input()
params: any
constructor() { } // <-- params not set
ngOnChanges() { } // <-- anytime params changes
ngOnInit() { } // <-- once when the component is mounted
}
Related
In my angular component I have ngAfterViewInit method which contains some statements that I want to execute after the view is initialized cause it contains some DOM manupulations.
My question is when some parameter is changed I want to run the code inside ngAfterViewInit.
You could define an EventEmitter in the constructor, subscribe to it in the ngAfterViewInit() function to update those values, emit it in the ngAfterViewInit() function, and then emit it again every single time you want to call it in subsequent areas of the component. Here is an example:
import { EventEmitter } from '#angular/core';
export class MyComponent implements AfterViewInit {
public myEvent: EventEmitter<void>;
public constructor() {
this.myEvent = new EventEmitter<void>();
}
public ngAfterViewInit(): void {
// This is how you call the function to do what you want to do with your DOM manipulations below. You can also call this exact function even from the HTML, if you wish to do so (see HTML example).
this.myEvent.emit();
this.myEvent.subscribe(
() => {
// Do whatever actions that you need to do here to perform your DOM manipulations.
},
(err: Error) => console.error(err);
);
}
public emitMyEvent(): void {
this.myEvent.emit();
}
}
<my-component (click)="myEvent.emit()"></my-component>
<!-- or -->
<my-component (click)="emitMyEvent()"></my-component>
If you want to execute those statements on every changes than it is better to write those in ngAfterViewChecked().From the docs:
A lifecycle hook that is called after the default change detector has completed checking a component's view for changes
A callback method that is invoked immediately after the default change detector has completed one change-check cycle for a component's view.
So it will be called on every subsequent changes.
More information can also be found on the Lifecycle Hooks#AfterView docs
If your parameter is available as an Observable, you can just subscribe to it in the ngAfterViewInitmethod. If your parameter is not yet available as Observable, I suggest you take a look at the BehaviourSubject class. With this, you can control when the Observable will emit a new value + It will be triggered with the last value when you subscribe to it
You declare ordinary class methords in the class body, and later define them in the context of ngAfterviewInit.
here's a simple use case example:
export class ViewtestComponent implements OnInit, AfterViewInit{
#ViewChild('someElementMewantedToDoAction') elRef: ElementRef;
constructor() { }
ngOnInit(): void {
}
changeVal;
ngAfterViewInit() {
this.changeVal= (useME) => {
// some action
this.elRef.nativeElement.innerText++;
}
}
Later, use the method in template as
// Do Action= Button value will increment 11, 12 13 ... on each button click.
<button class="button success" (click)="changeVal($emit)>10</button>
I have solved my problem by implementing OnChanges and call ngAfterviewInit for changes other than the firstchange.This way I will make sure that the view is initiallized. By the way the variable subjected to change(changed_var) holds data used in DOM manipulation.
ngOnChanges(changes: SimpleChanges){
if(!changes.changed_var.isFirstChange()){
this.ngAfterViewInit();
}
}
My components often start out by having multiple #Input and #Output properties. As I add properties, it seems cleaner to switch to a single config object as input.
For example, here's a component with multiple inputs and outputs:
export class UsingEventEmitter implements OnInit {
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() => this.prop1Change.emit(this.prop1 + 1));
}
}
And its usage:
export class AppComponent {
prop1 = 1;
onProp1Changed = () => {
// prop1 has already been reassigned by using the [(prop1)]='prop1' syntax
}
prop2 = 2;
onProp2Changed = () => {
// prop2 has already been reassigned by using the [(prop2)]='prop2' syntax
}
}
Template:
<using-event-emitter
[(prop1)]='prop1'
(prop1Change)='onProp1Changed()'
[(prop2)]='prop2'
(prop2Change)='onProp2Changed()'>
</using-event-emitter>
As the number of properties grows, it seems that switching to a single configuration object might be cleaner. For example, here's a component that takes a single config object:
export class UsingConfig implements OnInit {
#Input() config;
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() => this.config.onProp1Changed(this.config.prop1 + 1));
}
}
And its usage:
export class AppComponent {
config = {
prop1: 1,
onProp1Changed(val: number) {
this.prop1 = val;
},
prop2: 2,
onProp2Changed(val: number) {
this.prop2 = val;
}
};
}
Template:
<using-config [config]='config'></using-config>
Now I can just pass the config object reference through multiple layers of nested components. The component using the config would invoke callbacks like config.onProp1Changed(...), which causes the config object to do the reassignment of the new value. So it seems we still have one-way data flow. Plus adding and removing properties doesn't require changes in intermediate layers.
Are there any downsides to having a single config object as an input to a component, instead of having multiple input and outputs? Will avoiding #Output and EventEmitter like this cause any issues that might catch up to me later?
personally if I see I need more than 4 inputs+outputs, I will check my approach to create my component again , maybe it should be more than one component and I'm doing something wrong.
Anyway even if I need that much of input&outputs I won't make it in one config, for this reasons :
1- It's harder to know what should be inside inputs and outputs,like this:
(consider a component with to html inputs element and labels)
imagine if you got only 3 of this component and you should comeback to work on this project after 1 or 2 month, or someone else gonna collaborate with you or use your code!.
it's really hard to understand your code.
2- lack of performance. it's way cheaper for angular to watch a single variable rather than watching an array or object. beside consider the example i gave you at first one, why you should force to keep track of labels in which may never change alongside with values which always are changing.
3- harder to track variables and debug. angular itself comes with confusing errors which is hard to debug, why should I make it harder. tracking and fixing any wrong input or output one by one is easier for me rather than doing it in one config variable which bunch of data.
personally I prefer to break my components to as small as possible and test each one. then make bigger components out of small ones rather than having just a big component.
Update :
I use this method for once input and no change data ( like label )
#Component({
selector: 'icon-component',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
inputs: ['name', 'color']
});
export class IconComponent implements OnInit {
name: any;
color: any;
ngOnInit() {
}
}
Html:
<icon-component name="fa fa-trash " color="white"></icon-component>
with this method angular wont track any changes inside your component or outside.
but with #input method if your variable changes in parent component, you will get change inside component too.
I would say it could be OK to use single config objects for Inputs but you should stick to Outputs at all the time. Input defines what your component requires from outside and some of those may be optional. However, Outputs are totally component's business and should be defined within. If you rely on users to pass those functions in, you either have to check for undefined functions or you just go ahead and call the functions as if they are ALWAYS passed within config which may be cumbersome to use your component if there are too many events to define even if the user does not need them. So, always have your Outputs defined within your component and emit whatever you need to emit. If users don't bind a function those event, that's fine.
Also, I think having single config for Inputs is not the best practice. It hides the real inputs and users may have to look inside of your code or the docs to find out what they should pass in. However, if your Inputs are defined separately, users can get some intellisense with tools like Language Service
Also, I think it may break change detection strategy as well.
Let's take a look at the following example
#Component({
selector: 'my-comp',
template: `
<div *ngIf="config.a">
{{config.b + config.c}}
</div>
`
})
export class MyComponent {
#Input() config;
}
Let's use it
#Component({
selector: 'your-comp',
template: `
<my-comp [config]="config"></my-comp>
`
})
export class YourComponent {
config = {
a: 1, b: 2, c: 3
};
}
And for separate inputs
#Component({
selector: 'my-comp',
template: `
<div *ngIf="a">
{{b + c}}
</div>
`
})
export class MyComponent {
#Input() a;
#Input() b;
#Input() c;
}
And let's use this one
#Component({
selector: 'your-comp',
template: `
<my-comp
[a]="1"
[b]="2"
[c]="3">
</my-comp>
`
})
export class YourComponent {}
As I stated above, you have to look at the code of YourComponent to see what values you are being passed in. Also, you have to type config everywhere to use those Inputs. On the other hand, you can clearly see what values are being passed in on the second example better. You can even get some intellisense if you are using Language Service
Another thing is, second example would be better to scale. If you need to add more Inputs, you have to edit config all the time which may break your component. However, on the second example, it is easy to add another Input and you won't need to touch the working code.
Last but not least, you cannot really provide two-way bindings with your way. You probably know that if you have in Input called data and Output called dataChange, consumers of your component can use two-way binding sugar syntax and simple type
<your-comp [(data)]="value">
This will update value on the parent component when you emit an event using
this.dataChange.emit(someValue)
Hope this clarifies my opinions about single Input
Edit
I think there is a valid case for a single Input which also has some functions defined inside. If you are developing something like a chart component which often requires complex options/configs, it is actually better to have single Input. It is because, that input is set once and never changes and it is better to have options of your chart in a single place. Also, the user may pass some functions to help you draw legends, tooltips, x-axis labels, y-axis labels etc.
Like having an input like following would be better for this case
export interface ChartConfig {
width: number;
height: number;
legend: {
position: string,
label: (x, y) => string
};
tooltip: (x, y) => string;
}
...
#Input() config: ChartConfig;
The point of having the Input besides its obvious functionality, is to make your component declarative and easy to understand.
Putting all the configs in one massive object, which will grow definitely (trust me) is a bad idea, for all the above reasons and also for testing.
It's much easier to test a component's behaviour with a simple input property, rather than supplying a giant confusing object.
You're going backwards and thinking like the way jQuery plugins used to work, where you'd call a function called init and then you provide a whole bunch of configuration which you don't even remember if you should provide or not, and then you keep copy pasting this unknown and ever-growing object across your components where they probably don't even need them
Creating defaults is extremley easy and clear with simple Inputs whereas it becomes a little bit messy with objects to created defaults.
If you have too many similar Input, Outputs, you can consider below :
1- You can create a Base class and put all your Input/Outputs that are similar and then extend all your components from it.
export class Base{
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
}
#Component({})
export class MyComponent extends from Base{
constructor(){super()}
}
2- If you don't like this, you can use composition and create a reusable mixin and apply all your Input/Outputs like that.
Below is an example of a function that can be used to apply mixins, NOTE may not necessarily be exactly what you want, and you need to adjust it to your needs.
export function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
And then create your mixins :
export class MyMixin{
#Input() prop1: number;
#Output() prop1Change = new EventEmitter<number>();
#Input() prop2: number;
#Output() prop2Change = new EventEmitter<number>();
}
applyMixins(MyComponent, [MyMixin]);
3- You can have default properties for inputs so you only override them if you need:
export class MyComponent{
#Input() prop1: number = 10; // default
}
Are there any downsides to having a single config object as an input to a component, instead of having multiple input and outputs?
Yes, when you want to switch to the onpush change detection strategy, which is often needed in bigger projects to mitigate performance issues caused by too many render-cycles, angular will not detect changes that happened inside your config object.
Will avoiding #Output and EventEmitter like this cause any issues that might catch up to me later?
Yes, if you start to move away from #Output and in your template directly operate on the config object itself, then you are causing side-effects in your view, which will be the root of hard-to-find bugs in the future. Your view should never modify the data it get's injected. It should stay "pure" in that sense and only inform the controlling component via events (or other callbacks) that something happened.
Update: After having a look at the example in your post again, it looks like you did not mean that you want to directly operate on the input model but pass event emitters directly via the config object. Passing callbacks via #input (which is what you are implicitly doing) also has it's drawbacks, such as:
your component gets harder to understand and reason about (what are its inputs vs its outputs?)
cannot use banana box syntax anymore
If you want to bundle input parameters as an object, i'd suggest to do it like this:
export class UsingConfig implements OnInit {
#Input() config: any;
#Output() configChange = new EventEmitter<any>();
ngOnInit() {
// Simulate something that changes prop1
setTimeout(() =>
this.configChange.emit({
...this.config,
prop1: this.config.prop1 + 1
});
);
}
}
You are creating a new config object when changing a property.
You are using an Output-Event to emit the changed config object.
Both points ensure that ChangeDetection will work properly (assuming you use the more efficient OnPush strategy). Plus it's easier to follow the logic in case of debugging.
Edit:
Here's the obvious part within the parent component.
Template:
<using-config [config]="config" (configChange)="onConfigChange($event)"></using-config>
Code:
export class AppComponent {
config = {prop1: 1};
onConfigChange(newConfig: any){
// if for some reason you need to handle specific changes
// you could check for those here, e.g.:
// if (this.config.prop1 !== newConfig.prop1){...
this.config = newConfig;
}
}
Im building an angular 2 app, and i need a way to get a reference to a service/class that is injected/instantiated by angular framework.
I have a generic class...lets call it Custom class, that i use throughout the site. This class, however is instantiated by me, not angular 2.
Now, i need to use some of the services instantiated by angular 2 inside that class.
i.e
// this is not angular component/service
export default class Custom {
constructor(properties) {
}
doSomething() {
// UserService is a service that is injected into other componetns constructors, but this class is not a component.
// Here i need a ref to the singleton 'UserService' made by angular
let userService = someAngularFunction.getInstance('UserService');
userService.doIt();
}
}
// in some component.
export class MyComponent implements OnInit {
doAnotherThing() {
let c = new Custom('some generic params');
c.doSomething();
}
}
// in some n number of other components, repeat the above.
Note, i know that i could inject the 'UserService' into
MyComponent, and from MyComponent, pass it down to new Custom() constructor. But, since MyComponent itself doesn't use that service, and
Custom class is instantiated in many other places, id like to move that dependency into Custom class.
Is there a way to do that ?
If not, whats the second best option.
As far as I know you have two choices:
1) Manually pass the service into the Custom class constructor.
let x = new Custom(this.userService);
2) Make the Custom class a service as well. Then it will participate in Angular's DI.
See this article for more information: https://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
I have a method that is subscribing to an event from a pub sub messaging service. In the callback I am wanting to define a class property. When I try to assign the property value, it returns as undefined. I understand that the reference to 'this' changed from the class to the method, but I need it to have access to the class's 'this' property. How can I assign the value to the class property 'this.icon' inside my callback method?
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription'
import { Events } from '../shared/messages/events';
import { MessageService } from '../shared/messages/message.service';
export class Component implements OnInit, OnDestroy {
public icon: string;
private subscription: Subscription;
constructor() { this.btnClickSubscribe();}
private btnClickSubscribe(): void {
this.subscription = this.messageService
.subscribe(Events.btnClick, (payload) => {
this.icon = 'fa-trash-o';
console.log(this.icon) //logs the correct value, 'fa-trash-o'
//but it's only available inside this context. I need it in the
//class context
});
}
Since this is an asynchronous event, this.icon will initially be undefined outside the callback, no matter what you do. Check this one about more info: How do I return the response from an Observable/http/async call in angular2?
You mentioned you are passing icon to a child via #Input() then make use of ngOnChanges in the child, which captures the changes happening to icon. In the ngOnChanges you can make a condition, that executes whatever logic you want to do, after the value has been set to to icon, so something like this in your child:
#Input() icon;
ngOnChanges() {
if(this.icon) {
console.log('icon is set, now do something with it!')
}
}
And if you have problems with the view, there are some possible solutions, like using the safe navigation operator, more info here: Cannot read property "totalPrice" of undefined
Here's a
Demo
Hope this helps! :)
How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/