Dynamic instantiation of Components? - angularjs

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

Related

How can i dynamically change images as Background in TailwindCSS?

I want to make a carousel, where the background is changing, i don't want to use the <img/> tag! I set the value as described in the documentation: https://tailwindcss.com/docs/background-image#arbitrary-values
My Code:
import React from 'react';
type CarouselProps = {
img: string;
};
const Carousel = ({ img }: CarouselProps) => {
return (
<div
className={`col-span-full bg-[url(${img})] bg-cover grid grid-cols-12 gap-6`}
> ...
</div>
);
};
When i set the String i pass to the Component hardcoded it works but when i use curly Braces and $ it doesn't. In addition i don't want to define my Background-Images in the tailwind.conf.js
The Error:
ERROR in ./src/index.css (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5]
.use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[2]!
./node_modules/source-map-loader/dist/cjs.js!./src/index.css) 9:36-70
i don't want to define my Background-Images in the tailwind.conf.js
Well you have to. What you're trying to do isn't supported.
The way Tailwind scans your source code for classes is intentionally
very simple — we don’t actually parse or execute any of your code in
the language it’s written in, we just use regular expressions to
extract every string that could possibly be a class name.
so tailwind has no idea what your React code actually means. So it's simply not going to work.
Tailwind does not support dynamic class names:
Don't construct class names dynamically
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
you should customise your theme to include the image url:
You can add your own background images by editing the
theme.backgroundImage section of your tailwind.config.js file:
tailwind.config.js
module.exports = {
theme: {
extend: {
backgroundImage: {
'hero-pattern': "url('/img/hero-pattern.svg')",
'footer-texture': "url('/img/footer-texture.png')",
}
}
}
}
The solution is to use the style attribute. Thanks for helping :)
<div
className="col-span-full bg- bg-cover grid grid-cols-12 gap-6"
style={{
backgroundImage: `url(${img})`,
}}
>

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)
}

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 - 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

How to define place where dynamic components would be injected?

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.

Resources