Angular 2: Using Component Input to Pass Nested Arrays - arrays

Is there a better solution to passing complex objects to child components when the objects consist of nested arrays?
Here's my issue: in the partial html that appears in the child component, you'll have to represent nested arrays like this: {{animal.quadripeds[2].dogs[4].furColor}}
The index values are hard-coded. It'd be nicer to see it like this, for instance:
animal.quadripeds.find(q => q.isDirty == true).dogs.find(d => d.isDirty == true).furColor. Unfortunately, you can't use the .find() in {{}}
Here's a plnkr for your enjoyment: Nested Arrays via Component Input
Thanks!

You can't use find method in your template, but it does not mean that you can't use it in your component, for example :
import {Input, Component, OnInit} from 'angular2/core';
#Component(...)
export class ChildComponent implements OnInit {
#Input() transport: Transport;
private _valueToDisplay;
ngOnInit() {
this._valueToDisplay = animal.quadripeds
.find(q => q.isDirty == true)
.dogs.find(d => d.isDirty == true)
.furColor;
}
get valueToDisplay() {
return this._valueToDisplay;
    }
}
Two things:
Note that I use the OnInit interface : this is because your input property will not be initialized yet in your constructor (so be careful to implement your initialization logic in the ngOnInit function).
You probably have to handle the same logic when your input property is updated, you can implement the ngOnChanges function (or use a setter for your input property).
Here is your updated plunkr: http://plnkr.co/edit/BTzAfO6AGSLnOn8S1l24
Note that, as suggested by #dfsq, this logic should probably go in a service.

Related

Update List with data from array

Hello Everyone,
Iam trying to create a small Application which manage tasks for employees. In one part of the Application the user can add an failed call attempt.
So if he clicks on a button a function get called which should insert a new call object in the calls array of the CallService. After that the new call should be shown in the view. But it doesn't.
This is the View
<mat-list>
<div mat-header style="color:#043755; font-weight: 700; font-size: 1.8rem; text-align: center;">Anrufe:</div>
<mat-list-item *ngFor="let call of calls;">
<mat-icon mat-list-icon>phone</mat-icon>
<div mat-line style="font-size: 1.2rem;">{{call.number}}</div>
<div mat-line style="font-size: 1.2rem;"> {{call.date | date}} </div>
<div mat-line style="font-size: 1.2rem; color:chocolate;">{{call.status}}</div>
</mat-list-item>
*it should load the calls with the ngFor derective.
Here is my Component
import {Component, OnInit} from '#angular/core';
import { Call, CallService } from '../call.service';
/**
* #title List with sections
*/
#Component({
selector: 'app-missed-calls',
styleUrls: ['./missed-calls.component.css'],
templateUrl: './missed-calls.component.html',
})
export class MissedCallsComponent implements OnInit {
public calls: Call[] = [];
constructor(private _callService: CallService) { }
ngOnInit() {
this.calls = this._callService.getCalls();
}
}
I injected the service here and in the ngInit I "fill" my array.
And here is my Service
import { Injectable } from '#angular/core';
export interface Call {
number: string;
date: Date;
status: string;
}
#Injectable({
providedIn: 'root'
})
export class CallService {
constructor() { }
calls: Call[] = [
{number: '0677/61792248',
date: new Date('1/1/14'),
status: 'Verpasst'},
];
getCalls() {
return this.calls;
}
addCall(call: Call) {
this.calls = [...this.calls, call];
}
}
It would be nice if someone could help me to solve the problem! :D
Quickfix
A quick fix for you would be to make the Injectable public
public _callService: CallService
and updating your ngFor like this
<mat-list-item *ngFor="let call of _callService.calls;">
Another solution for you would be to change addCall method to do this:
this.calls.push(call)
I'll explain why
Explanation
In js, objects are saved by reference. This means they have an internal ID that points to the memory. This is what you share when you do return this.calls
You create the calls object on the service. Let's say it's internal id is js_id_123 and you return this.calls meaning that in js you return memory:js_id_123 if that makes sense.
so now calls on the component and calls on the service are pointing to the same memory object. So any modifications to that object would result in an update for the ngFor.
Component.calls ->> js_id_123
Service.calls ->> js_id_123
BUT:
in your addCalls you override the js_id_123 by creating a new object and assigning it to Service.calls
[...this.calls, call]
(this creates a new object, with values from the old object). So, let's say this new object is js_id_777, now your service has calls js_id_777, and your component js_id_123.
Component.calls ->> js_id_123
Service.calls ->> js_id_777
Your component will not get the updates, because you are updating the object js_id_777 referenced in the service only, so the calls in the component does not get those updates, so the ngFor does not have anything new to show.
This brings me to both of my solutions. You have multiple sources of truth.
So, if you use push, you are not changing the object reference, so it works. If you use the service object calls directly, you can change it's reference, but ngFor will pick it because it is reading that particular property, so if it changes, ngFor updates accordingly (not for the one in the component, that has not changed, therefore no need to update).
Maybe this makes little sense.
Performance suggestion
Also, destructuring ([...this.calls, calls]) has to go through the whole array. So the bigger your array gets, the slower your app will be at adding a new one, because it has to recreate a full array. In this case, I suggest you use push, as it doesn't need to iterate the array again, it just adds a new one.
If you have any questions let me know
What I would do:
I would change the service to use push instead of destructuring, for performance reasons. I then would make the property of the service the only source of truth (meaning, I don't need the calls property on the component).
Finally, there's no need for this service to be a service. It has no Dependency Injection or need to be a Service. So just make it a plain Object, call it CallsManager, initialize it on your component: callsManager = new CallsManager();, and then use it inside your ngFor
<mat-list-item *ngFor="let call of callsManager.calls;">
this is cleaner, keeps only one source of truth, and removes the cumbersomeness that comes from creating services (try to make them services when you need to inject something.. like httpClient or another service. If no Dependency Injection is needed, a plain object is preferable)
Instead of pushing new values,you were just assigning it to array ,so issue occurred.
Just change your adding function
// From
addCall(call: Call) {
this.calls = [...this.calls, call];
}
// To
addCall(call: Call) {
this.calls.push(call)
}

How to distinguish between a prop and a method with the same name, in Vue?

I recently switched from React to Vue. In React, i often had a method in a child component, that would look something like this:
onClick = (e)=>{
const val = e.target.value;
this.props.onClick(val)
}
I see that Vue is very "free", and allows you to treat component methods and props as "the same thing".
Is there some way to distinguish between the two, like with this.props? What is the convention regarding this issue?
Props, data, computed properties and methods end up as properties on Vue instance - members may also include lifecycle hooks when a component is declared as a class. As with any other class, their names may collide.
In case there are members that are private (onClick method) and are parts of public API (onClick prop), they can be named differently, e.g. with underscore naming convention that is common in JS OOP:
...
props: {
onClick: Function
},
methods: {
_onClick() {...}
}
...
The use of naming conventions for private properties is suggested by Vue style guide.
In composition API and Vue 3, there is a clear distinction in setup because props object is available, so it could be done similarly to how the question suggests. There is no difference between props and instance properties in templates so the rest of the answer is still applicable.
You should take advantage of another Vue technique, instead of passing a function, using its event emitter system:
// MyComponent:
methods: {
onClick(e) {
const val = e.target.value;
this.emit('click', val);
}
}
...
<button #click="onClick">Click me</button>
//When you actually use your component
<MyComponent #click="() => { console.log('delegated onClick!'); }"/>
As mentioned in one of the comments try to avoid using duplicate names for props, methods, etc...

How can I add an unchangeable property to a React JS component?

Suppose I have a React JS component, and for the sake of ease of use, I want that component to have access to an array that maps indexes to text.
For example, let's say that in scenario A I pass the component the ID 1, whereas in scenario B I pass the component the ID 3. I want the component to 'know' what to do with those IDs, so I want the component itself to have an array like the following:
[
0 => 'Text for scenario 0',
1 => 'Text for scenario 1',
2 => 'Text for scenario 2',
]
Now there are two ways that I personally know of that would allow this component to have access to that array. I could define it as part of the state in the constructor:
constructor() {
this.state = {
// put the array here
}
}
Or, I could make it a default prop:
Component.defaultProps = {
// put the array here
}
Which would then allow the component to access the array from either this.state or this.props.
However, I don't like either of these options, because both allow the array to be changed. If I put it in state, then the array can be changed from (almost) anywhere within the component, and if I put it in props, then the component could be passed an alternative array as a prop.
So: is there a way, in React JS, to define a static, unchangeable property on a component?
You shouldn't put things in the React state that aren't going to be reactive or manipulated in some way and setting it as a default prop is not a great solution either as that implies that sometimes the component will receive a prop of that name.
One solution could be to set up a function which simply returns this list:
getIdList() {
return [];
}
However, I think the better solution would be to just define it as a const above the React component. Seeing as this data will never be changed it doesn't really need to live within the component itself.
So, that would like something like the following:
const idArray = [];
class SomeReactComponent extends React.Component {
Since every module has it's own scope you can just put the array locally in the module of your component (above the component). If you don't export it it will be private
const texts = [
'Text for scenario 0',
'Text for scenario 1',
'Text for scenario 2',
]
export default class MyComponent extends Component {
...you can access texts as a const here
}
You can save variables in more than just state and props, neither of which you want want to use here, since this array is neither the state of the component or a property being passed to it.
You can, as others have said, store the array elsewhere in the file and simply reference it within your component.
You can store the array as a property of your component, and just reference it as this.myArray where needed:
constructor(props) {
super(props);
this.myArray = [1, 2, 3];
}
Or, if the array is not going to be changing from one instance of the component to the next, you can store it as a static property of the component and reference it as MyComponent.myArray where needed:
static myArray = [1, 2, 3];
The solution to just place it elsewhere in the file is a good one, especially if it is a const variable or if you may ever want to export and use it elsewhere in your project.

Opening error popover from service in Angular 2

I would like to create a service which will be responsible for opening bootstrap popovers with errors and success communicates. I have two components ErrorComponent, SuccessComponent and one service called CommunicatesComponent. I would like to use this service to open popover like
service.error('some error')
service.success('success!')
And this should display popover with provided text as argument. What I am doing is setting component property in service like followed and use this property in this service:
ErrorComponent
export class ErrorComponent implements OnInit {
public text:string;
#ViewChild('errorPopover') private errorPopover: NgbPopover;
constructor(private communicatesService:CommunicatesService) {
}
public ngOnInit() {
this.communicatesService.setErrorComponent(this)
}
}
Service:
#Injectable()
export class CommunicatesService {
private errorComponent:ErrorComponent
public setErrorComponent(component:ErrorComponent) {
this.errorComponent = component;
}
public error(text:string) {
console.log(this.errorComponent)
// this.errorComponent.text = text;
}
}
Unfortunitelly, it seems that my component object is not provided well, because console log prints undefined. How it should be done?
Regards
There are two things I would change in your design
ErrorComponent and CommunicatesService depends on each other. It's good to avoid it - CommunicatesService could easily work with different components. So you could create an rx Observable public property of the service, so any component can subscribe to it. When service.success('success!'), the service will send the message text to the subscribers.
In ErrorComponent, you get the popover component as a #ViewChild. You could consider binding ErrorComponent.text to the popover directly (reversing the dependency).
These changes could solve the problems you have and make the design looser - easier to understand and maintain.

Angular 2 - Plug in dynamic data from API with parameter to HTML?

I'm fairly new to Angular 2, and I'm trying to find the best way to pass data to a nested component, call a method (REST API) using that data, and return the result into the parent HTML.
I understand how to pass the data - just use the #Input decorator on a variable in the child component. That gets the data in. Now I want to process that data and compute a result. I can't do that in the constructor of the child component, because in the constructor, the input value has not yet been passed. So where do I do it? Is there a render event I can use? I arrived at a solution that seems to work very well, I just don't know if I'm breaking some rules or causing issues.
Since I'm using Typescript, I can use setters. I defined a setter for the input value, so in the setter I can run code to process the data. Here is my working code:
import { Component, Input } from "#angular/core";
import { Http } from "#angular/http";
import { AppService } from "./../../services/appservice";
import { UserProfileService } from "./../../services/userprofileservice";
#Component({
selector: "displayname",
template: "{{displayName}}",
providers: [AppService, UserProfileService]
})
export class DisplayName {
public displayName: string;
private profiler: UserProfileService;
constructor(private appService: AppService) {
this.profiler = new UserProfileService(this.appService);
}
#Input("login")
set login(newLogin: string) {
this.profiler.getUserProfile(newLogin, (profile) => {
this.displayName = profile ? profile.displayName : newLogin;
});
}
}
What this code does is take a login name as input, fetch the user's display name, and set the displayName variable to the result. If the result is null, it just returns the login name. Using the #Input decorator on the setter works great, I've just never seen it done before in any examples so I don't know if there is a better way.
My HTML code looks like this (simplified for this example):
<tr *ngFor="let user of userList>
<td><displayname [login]="user.loginName"></displayname></td>
</tr>
Another way to ask this question is to back up to my basic use case, which I think must be very common. How do you dynamically insert data from a REST API into an HTML page? In this case I want to pass in the login name, and render the Display Name, that is fetched from a REST API. In this case I don't need any HTML formatting (you can see my template is just a variable, no HTML) - is there a simpler way to do this? Should I be using an #Output?
Thanks,
Randy

Resources