How to define place where dynamic components would be injected? - angularjs

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.

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 can I render a React component inside a DOM node retrieved via class name?

HTML:
<body>
<div class="root"></div>
</body>
JavaScript:
import ReactDOM from "react-dom";
function App() {
return ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementsByClassName("root")
);
}
export default App;
When this code gets compiled I get the following "Error: Target container is not a DOM element."
Does anyone know why?
You are fetching multiple elements instead of one. The second argument in ReactDOM.render should be an element, not a list of elements.
In case you want to go using class instead of id, it might be more prudent to use document.querySelector(".root") as your second argument.
document.getElementsByClassName() gives an array in return instead of an element.
You can either do something like this -
document.getElementsByClassName("root")[0]
or change do getElementById("root");
and change class="root" to id="root".
the method getElementsByClassName returns an array-like object and not a DOM element like react-dom requires (DOCS).
In case you want to quesry a single DOM element with a class name you can use the querySelector method which returns the first element within the document that matches the specified selector
document.querySelector(".root")

How to override all click handlers for an element type in the DOM using React?

I want to override the click handler for a elements.
So far, I managed to achieve this for my own code simply by wrapping my <a> elements in my own component, like this simplified example:
class Link extends React.Component {
onClick(event) {
// ...
event.preventDefault();
// ...
if (this.props.onClick)
this.props.onClick(event);
}
render() {
return <a href={this.props.href} onClick={(e) => this.onClick(e)}>{this.props.children}</a>
}
}
Instead of using <a href="...">, I just use <Link href="...">
Unfortunately, I need to use other components that have links inside them. Is there any way I can be notified of any DOM change so that I can apply this handler? I'd rather not have to re-write the dependencies; is there any way to change how they render?
I thought about using a visitor pattern to recursively visit all the children of all components, but I realised this might not return the results I expect as the elements will only exist when they're actually rendered.

Webpack & Css-loader. How to create dynamic className for React?

The project has the following reference, which returns a string:
const left = slide.imageLeft; // introLeft
And further renders it inside React Component. But it returns as a string styles.imageLeft and since webpack doest convert it into corresponding bundled class like 914u923asdsajdlj1l23 the styles are not applied.
<div className={`styles.${left}`}> </div>
P.S I did try to eval, but it drops 2 errors.
There is an internal error in the React performance measurement code. Did not expect componentDidMount timer to start while render timer is still in progress for another instance.
And
ReferenceError: styles is not defined
Can you please suggest the possible ways to achieve dynamic class generation for css-loader.
You have to define the style within the render(), or within the component definition, like this
render: function(){
var myStyle = {
// your style rules go here
};
return(
<div style={myStyle}>
{this.props.children}
</div>
)
}
in a way, this is already dynamic, because all you have to do is to change to style and it'll make sure that the component will re-render on update

Angular 2: Using Component Input to Pass Nested 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.

Resources