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)
}
Related
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
My components injected dynamical. But how to define place where they would be without any wrapping?
import { Component, ViewContainerRef, ComponentResolver } from '#angular/core';
constructor(
private apiService: ApiService,
private viewContainerRef: ViewContainerRef,
private componentResolver: ComponentResolver) {
}
create() {
this.componentResolver.resolveComponent(PieChart).then((factory) => {
this.viewContainerRef.createComponent(factory);
});
}
And i want to inject them in some DIV.
<div class="someClass">
<!--How inject them here? -->
</div>
There are two ways:
injecting the ViewContainerRef like in your question, then new components are added below this component
using a target element in your components template with a template variable like <div #target></div> and #ViewChild('target', {read: ViewContainerRef}) viewContainerRef:ViewContainerRef; to get the ViewContainerRef of this <div>. Added elements are again added below this <div>
createComponent() allows to pass an index. By default new components are always added at the end, but if you pass an index it is inserted at this position.
(not tested) with index 0 you should be able to add the new element before the target element.
If you add more than one component to the same ViewContainerRef you can pass an index to insert the new component before the elements added earlier.
In finale release, ComponentResolver is deprecated. Instead, We can use Compiler API to create ComponentFactory.
But how we can do this now?
I found this article http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2-rc-5/ but compiler does not have method compileComponentAsync.
Update: To clarify my sad verbiage below...
Given an array of JSON objects, a "Level" component and a "Sprite" component...
I'm stuck on the syntax to loop through the array and init a new "Sprite" for each JSON object and then add that "Sprite" to ... the template (?) of my "Level".
This is my "Level" template. I can hardcode the Sprite in and it works (as the image shows) but I'm scrambling to find the syntax to add an arbitrary number of Sprites.
<div>
<img src={ {imagePath}}/>
</div>
<div>
<app-sprite></app-sprite>
</div>
I'm a refugee Flex/Actionscript developer and am learning Angular 2. I "get" lots of it – but then I get stuck.
My little exercise here: based on some JSON (hard-coded now but later to come from a Service) is to populate a "Level" component with a background image, and then create and position some "Sprites" on the "Level".
Here's the way it looks now:
I've created a plunk of what I am doing. Where I am confused:
How do I dynamically create custom components the "angular" way? I've seen the tutorials with *ngFor to create li elements but the template for my "Sprite" component looks like this:
<div>
<img class="spriteFloater" src={{imagePath}}/>
</div>`
The above works if I hardcode the "imagePath" to a url – but if I try to use "eventData.imagePath" (where event data is a JSON obj) then it fails.
I can hard-code that a reference to that template into my "Level" ("app-sprite" tags) and it works but my attempts to generate "Sprites" on the fly have failed.
Also, I am trying to pass data to each "Sprite" and then use it in my template. I tried doing it through the constructor but that didn't work so a created a custom function to do it – and while it outputs the correct data to the console, the "Sprites" I am creating are not the ones being injected into the DOM.
Geez... sorry this is such a stupid, wordy post.
With Günter's nudge I was able to come up with this. I didn't know the syntax to use *ngFor correctly and then I had to figure out how to apply the passed in data to the style of the newly created sprites.
Guess I am not in Kansas Flex/Actionscript any more. This stuff is crazy... but I like it (I think).
<div>
<app-sprite *ngFor="let event of eventList" [eventData]="event"
></app-sprite>
</div>
import { Component, OnInit, Input } from '#angular/core';
import {Eventdata} from './eventdata'
#Component({
selector: 'app-sprite',
template:`
<div>
<img class = "spriteFloater" [src]="eventData.imagePath"
[style.left] = "eventData.x"
[style.top] = "eventData.y"
[style.background] = "eventData.id"/>
</div>`,
styles [`
.spriteFloater {
position: absolute;
background: black;
left: 50px;
top: 80px;
}
`]
})
export class Sprite implements OnInit {
#Input() eventData: Eventdata;
setEventData(data:EventData){
}
constructor() {
}
ngOnInit() {
}
}
I guess you just need to use
eventData?.imagePath
If you init data in ngOnInit() this is after Angular first tries to resolve bindings in the view. If eventData is null the access to imagePath throws. With ?. Angular accesses imagePath only when eventData is != null
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.
In Angular 1, change detection was by dirty checking the $scope hierarchy. We would implicitly or explicitly create watchers in our templates, controllers or components.
In Angular 2 we no longer have $scope, but we do override setInterval, setTimeout, et al. I can see how Angular might use this to trigger a $digest, but how does Angular determine what has changed, especially given that Object.observe never made it into browsers?
Example
Here is a simple example. An object defined in a service is updated in a setInterval. The DOM is recompiled each interval.
How is Angular able to tell that the AppComponent is watching the service, and that the value of an attribute of the service has changed?
var InjectedService = function() {
var val = {a:1}
setInterval(() => val.a++, 1000);
return val;
}
var AppComponent = ng.core
.Component({
selector: "app",
template:
`
{{service.a}}
`
})
.Class({
constructor: function(service) {
this.service = service;
}
})
AppComponent.parameters = [ new ng.core.Inject( InjectedService ) ];
document.addEventListener('DOMContentLoaded', function() {
ng.platform.browser.bootstrap(AppComponent, [InjectedService])
});
Angular creates a change detector object (see ChangeDetectorRef) per component, which tracks the last value of each template binding, such as {{service.a}}. By default, after every asynchronous browser event (such as a response from a server, or a click event, or a timeout event), Angular change detection executes and dirty checks every binding using those change detector objects.
If a change is detected, the change is propagated. E.g.,
If an input property value changed, the new value is propagated to the component's input property.
If a {{}} binding value changed, the new value is propagated to DOM property textContent.
If the value of x changes in a style, attribute, or class binding – i.e., [style.x] or [attr.x] or [class.x] – the new value is propagated to the DOM to update the style, HTML attribute, or class.
Angular uses Zone.js to create its own zone (NgZone), which monkey-patches all asynchronous events (browser DOM events, timeouts, AJAX/XHR). This is how change detection is able to automatically run after each asynchronous event. I.e., after each asynchronous event handler (function) finishes, Angular change detection will execute.
I have a lot more detail and reference links in this answer: What is the Angular2 equivalent to an AngularJS $watch?
Zone.js
Changes happen as a reaction to something, so in this respect they are asynchronous. They are caused by asynchronous actions, and in the browser world those are Events. To intercept those events angular uses zone.js, which patches JavaScript call stack (I beleive, someone correct me if I'm wrong) and exposes hooks that can be used to take other actions.
function angular() {...}
zone.run(angular);
If you imagine this angular function is the entire Angular, this would be how it is run in zone. By doing so Events can be intercepted and if they are triggered we can assume changes happen, and listen/watch for them.
ApplicationRef
In reality ApplicationRef creates the zone:
/**
* Create an Angular zone.
*/
export function createNgZone(): NgZone {
return new NgZone({enableLongStackTrace: assertionsEnabled()});
}
and class NgZone is created with few event emitters:
this._onTurnStartEvents = new EventEmitter(false);
this._onTurnDoneEvents = new EventEmitter(false);
this._onEventDoneEvents = new EventEmitter(false);
this._onErrorEvents = new EventEmitter(false);
that it exposes to the outside world via getters:
get onTurnStart(): /* Subject */ any { return this._onTurnStartEvents; }
get onTurnDone() { return this._onTurnDoneEvents; }
get onEventDone() { return this._onEventDoneEvents; }
get onError() { return this._onErrorEvents; }
When ApplicationRef is created it subscribes to the zone's events, specifically onTurnDone():
this.zone.onTurnDone
.subscribe(() => this.zone.run(() => this.tick());
Changes
When events are triggered tick() function is run which loops through every component:
this._changeDetectorRefs.forEach((detector) => detector.detectChanges());
and detects changes based on components' ChangeDetectionStrategy. Those changes are collected as an array of SimpleChange objects:
addChange(changes: {[key: string]: any}, oldValue: any, newValue: any): {[key: string]: any} {
if (isBlank(changes)) {
changes = {};
}
changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
return changes;
}
witch is available for us through onChanges interface:
export interface OnChanges {
ngOnChanges(changes: {[key: string]: SimpleChange});
}