I am in process on creating a small poc to try whether is it possible to load components according to a given json data structure. json will provide and array of component selectors. I tried a small example according to the reference materials i found via online. I used the "componentFactoryResolver" which is recommended way by Angular
I basically create couple of components and registered it with the entrycomponent decorator as follow in my module
entryComponents: [PersonalDetailsComponent, ContactDetailsComponent],
and in my app component i use the following code
#ViewChild('dynamicInsert', { read: ViewContainerRef }) dynamicInsert: ViewContainerRef;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
ngAfterViewInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(PersonalDetailsComponent );
const componentFactory2 = this.componentFactoryResolver.resolveComponentFactory(ContactDetailsComponent);
this.dynamicInsert.clear();
this.dynamicInsert.createComponent(componentFactory);
this.dynamicInsert.createComponent(componentFactory2);
}
and as you see i have to create component for each and every component i use. but having this an inside a loop might not be the best way to do it. i would much appreciate if any one could give me some heads up to do it in a proper way.
my actual json would look like something like this
{
"step":"1",
"viewed":false,
"stepDependant":{
"parentComponent":null,
"childComponent":null,
"varMap":null
},
"widgets":[
{
"Component":"shipper",
"inputs":[
{
"ServiceLine":"Export"
}
],
"outputs":[
],
"name":"Shipper Details"
},
{
"Component":"shipper",
"inputs":[
{
"ServiceLine":"Export"
}
],
"outputs":[
],
"name":"Consignee Details"
},
{
"Component":"status-of-shipment",
"inputs":[
],
"outputs":[
],
"name":"Status of Shipment"
}
]
}
much appreciate your inputs
As you have already found the componentFactoryResolver is the correct way to create components dynamically from code.
With this approach what I would do in your case is create a map or service that maps the component selectors to component types. This way you can then quickly lookup the type when you are creating the dynamic components from the JSON data. From the types you then resolve the factory and then add the components like in your sample.
If you have a predefined set of components that are known another alternative would be to define them all as <ng-template> in the parent component like this:
<ng-template #shipper><shipper ></shipper></ng-template>
<ng-template #statusOfShippment><status-of-shipment ></status-of-shipment></ng-template>
Then you can get the templates in the component by using the #ViewChild decorator.
#ViewChild('shipper')
shipperTemplate: TemplateRef<any>;
#ViewChild('statusOfShippment')
statusOfShippmentTemplate: TemplateRef<any>;
And then you can create the components in a simmilar fashion than with the factory.
this.dynamicInsert.createEmbeddedView(shipper);
this.dynamicInsert.createEmbeddedView(statusOfShippment);
What is good about this approach is that you can still have classic template binding and send a different context object to every template.
<ng-template #shipper><shipper [ServiceLine]="ServiceLine"></shipper></ng-template>
this.dynamicInsert.createEmbeddedView(shipper, {ServiceLine:"Export"});
This way you could directly send an object created from your JSON and configure the component bindings. If you use the component factory you need to set everything from code manually.
Related
I have started working on angular again after almost 2 years and i thought i could grasp from my previous knowledge but i am kind of stuck over here and cannot really understand what i can do.
I am using efc for getting data and in services i have the following method
shared-service.ts
getHotelDetails(val:any){
return this.http.get<any>(this.APIUrl+'Hotels/GetHotel/',val);
}
In ts i am getting the correct data by using the below code
mycomponent.ts
hotel$!:Observable<HttpEvent<any[]>>;
constructor(private service:SharedService, private _route:ActivatedRoute) {
}
ngOnInit(): void {
var Id = this._route.snapshot.params['id'];
this.hotel$ = this.service.getHotelDetails(Id);
console.log(this.hotel$);
}
mycomponent.html
I am trying to use the hotel object in html but cannot iterate or find the attributes inside. The api call is giving a json like the below for api:
https://localhost:44372/api/Hotels/GetHotel/1
{
"hotelId": 1,
"name": "pizza",
"addresses": [],
"comments": [],
"hotelFoods": []
}
I just need the code in which i can fetch the name from hotel$ attribute.
I tried ngfor and directly accessing hotel$.id
What you get back from the service is an Observable, you need to subscribe to it in order to access the data. The easiest way to do this in a html template is to use the async pipe.
You need something like this:
{{ (this.hotel$ | async).hotelId }}
Although I feel this answer is relatively close to my problem and got some reputation, I don't get it right. I read a lot of posts on how to use the "new" style of Observer-pattern ((...).pipe(map(...)).subscribe(...) and *ngFor="... | async") in Angular and now also stumbled across How to Avoid Observables in Angular. I don't want to avoid reactive behaviour; I want to have changes in the REST-API to be reflected "live" to the user without reloading the Observer. That's why I want to subscribe an object (and therefore also its properties) to an Observable (and the 'might-be-there' values from the data-stream in it), right?
In my template I have:
<p>Werte:<br><span *ngFor="let attribute of _attributes | slice:0:5; index as h">
{{attribute.name}}: <strong>{{getParamNameAt(h)}}</strong> </span><br>
<span *ngFor="let attribute of _attributes | slice:5: _attributes.length; index as h">
{{attribute.name}}: <strong>{{getParamNameAt(h + 5)}}</strong> </span></p>
In my component I have:
private _attributes: Attribute[];
constructor(private attributesService: BkGenericaAttributesService) {
attributesService.getAttributes().subscribe({ next: attributes => this._attributes = attributes });
}
getParamNameAt(h: number): string {
let attrVal = this.bkHerstArtNr.charAt(h);
return attributes[h].subModule.find(param => param.subModuleValue === attrVal).subModuleName;
}
and as service I have:
const localUrl = '../../assets/json/response.json';
#Injectable()
export class BkGenericaAttributesMockService implements BkGenericaAttributesService {
constructor(private http: HttpClient) {
}
getAttributes(): Observable<Attribute[]> {
return this.http.get<Attribute[]>(localUrl).pipe(
tap((attributes : Attribute[]) => attributes.map((attribute : Attribute) => console.log("Piping into the http-request and mapping one object after another: " + attribute.name))),
map((attributes : Attribute[]) => attributes.map((attribute : Attribute) => new Attribute(attribute.id, attribute.name, attribute.title, attribute.description,
(attribute.parameters ? attribute.parameters.map((parameter : Parameter) => new Parameter(parameter.id,
parameter.name, parameter.value)) : [])))));
}
My problem running the application at this point is the 'to-create-on-stream' Attribute-objects and "nested" Parameters[]-array (created by pushing Parameter-objects into it) pushed into the _attributes-array from the httpClient's Observable: Piping into the http-request and mapping one object after another: undefined.
Apart from this - is my construct the right way to read values from a JSON-file (or alternatively an API-stream, which may change while a user visits the SPA) into properties of multiple objects displayed on the Angular view?
With the answer mentioned above - or the way I thought I have to translate it into my code - I start to doubt that I'm really understanding (and using) the reactive Angular way with Data-Providers <= Observables => Operators => Subscribers and finally Observers displayed to the user.
I really am confused (as you can read), because a lot of answers and descriptions that I found so far use either older patterns (before Angular 5.5?) and/or partially contradict each other.
Do I handle the API-changes in the right place? Has the array for the template's *ngFor-directives to be an Observer (handled with | async) or will the changes of respectively within the array be handled by the model behind the template and the template grabs its new values (with interpolation and property binding) and also directives with a change in the components properties without asyncing?
Briefly:
Is there a for-dummies default instruction to "stream-read" values from a http-request into multiple Typescript-objects and their properties concurrently displayed in a template with on-stream-changing directives rendered only in the relevant DOM nodes, the Angular 8 opinionated way? "Stream-reading" meaning: pulling and pushing (only) if there are changes in the API, without wasting resources.
If I understand right, you want the server to push changes to the client, as soon as they happen, so that the client can react and render accordingly, right ?
this.http.get is a single call, that will resolve with a snapshot of the data. Meaning that it won't automatically update just because some data changed in your backend.
If you want to notify the client about new data or even send that data directly to the client, you'll need websockets.
There is also some problems with code:
if you .subscribe(), you'll need to .unsubscribe(), otherwise you'll end up with a memory leak.
param => param.value === attrVal, where is attrVal coming from, I don't see it being set aynwhere in the method ?
You should use the async pipe, as it unsubscribes automatically for you.
You don't neet to create class instances via new in your service, instead your Attribute typing should be an interface.
I have a component like this:
<my-dropzone value:"....">
<h1>upload</h1>
<p>your files</p>
</my-dropzone>
and my vuejs component is:
<template>
<div>
<div class="dropzone"></div>
<slot></slot>
</div>
</template>
<script>
....
export default {
props:{
....
},
components: {
vueDropzone: vue2Dropzone
},
data() {
return {
arrayFiles: [],
dropzoneOptions: {
....
dictDefaultMessage: // <------ I want to assign slot data here
,
....
},
mounted() {
.....
},
methods: {
.....
},
computed: {
.....
}
}
</script>
Now I want to get slot html data and assign to some local variable of component and also use in method. Is it possible? Is there any better solution to pass html to data variable
You can use Vue.js vm.$slots to access the slot children as HTML nodes. It is the only recommended way. Official Vue.js docs really explain it well.
You can use render() function for your components instead of template. Slots are designed to be used effectively with a render function. Without render function, using vm.$slots is not really that useful.
Long explanation:
Vue slots are not designed to work the way you need. Imagine slots as a rendering area. Also, a slot doesn't really contain HTML as a string. It is already passed from virtual-dom and rendered as HTMLElements on screen. By the time code is processed, html as a string has transformed into a render function.
Alternately, If you need to really access the underlying HTML nodes, then consider using plain DOM API like querySelector(), etc. As far as the $refs is concerned, Content within the slot is owned by the parent component and thus you cannot use it.
I'm working on an Angular 2 application, and I'm trying to use JSON data, either local/mocked or fetched via HTTP, and display it on a component. I have an injectable service that will do the fetching/mocking -
import { Injectable } from 'angular2/core';
#Injectable()
export class TestService {
testString:string = "";
testDetails: string = "";
constructor() { }
getTestDetails(): Promise<string> {
this.testDetails = {
"status": "success",
"message": "Data save successful",
"data": {
"Random_Data_1": "Random Data 1",
"Random_Data_2": "Random Data 2"
}
};
return Promise.resolve(JSON.stringify(this.propertyDetails));
}
}
And then I have a component that uses the service via Dependency Injection -
import { Component, OnInit } from 'angular2/core';
import {TestService} from "./test.service";
#Component({
selector: 'test',
templateUrl: './test.component.html',
styleUrls: []
})
export class TestComponent implements OnInit {
testDetails: string = "";
constructor(private testService: TestService) { }
ngOnInit() {
this.display();
}
display(): void {
this.testService.getTestDetails()
.then(
testDetails => {
this.testDetails = JSON.parse(testDetails);
},
errorMessage => {
console.error("Something failed trying to get test details");
console.error(errorMessage);
}
);
}
}
The component HTML -
<div class="content">
<p> Test Details </p>
<p> {{ testDetails.data.Random_Data_1 }} </p>
</div>
The problem is, the HTML is erroring out trying to display the items in the testDetails JSON. I initially used it with md-tabs, so the first try would error out, but the other tabs would read the data fine. Also, the ngOnInit would be called twice when the error occurs. I have narrowed it down to the data coming in and the object types that is causing me the headache.
I know I can create a Details class and declare testDetails of type Details, and then map the JSON data into the class, but the thing is, I want to work with generic data, and only know a few components that will be present in the data. Is there a way to read the JSON, and use the data without having to define a separate class for each scenario ?
I have a plunker with the most basic stuff set up. The actual setup runs fine on my local system up until where I try to access the JSON data in the HTML, at which point the browser throws a cryptic error. The skeleton code doesn't even run on Plunker. That said, the structure in the Plunker defines the structure of my app and the data flow. Plunker with the basic setup
What is the best way to achieve this ? What is the standard/best practice to do this ?
Throwing another option out there, since you asked about best way to achieve this. Might not be the best idea, this is subjective ;) But if I were you...
Thinking about the future, where you will use real backend, it could be nice to use mock json file. If/when you move over to a real backend, you wouldn't basically need to change anything else but the url of the requests :)
So I set up a simple example for you. Here I used Observables, but you can use Promises if you prefer that. Here's more info on HTTP if you want/need to read up on that. Most important thing is that you have the HttpModule imported in your app module.
You have your file with JSON and in your service make http-requests to that:
getTestDetails() {
return this.http.get('src/data.json')
.map(res => res.json())
}
Your display-method:
display() {
this.testService.getTestDetails()
.subscribe(data => {
this.testDetails = data;
});
}
And in the template use the safe navigation operator to safeguard null/undefined values:
<div class="content">
<p> Test Details </p>
<p> {{ testDetails?.data?.Random_Data_1 }} </p>
</div>
Here's a
Demo
As said, this is to give another approach on how to implement the things you want to achieve, and this would probably be my preferred way :)
Use
<p *ngIF="testDetails.data.Random_Data_1 "> {{ testDetails.data.Random_Data_1 }} </p>
This is because there is no data initially.Hope this helps you.
This question already has answers here:
How do I initialize a TypeScript Object with a JSON-Object?
(18 answers)
Closed 6 years ago.
I've been following the Angular2 getting started documentation to get my own app off the ground, I've been successful in retrieving JSON objects from a local file and displaying them in angular2 templates. However currently it relies on the angular classes exactly matching the structure of the objects in my JSON file.
Eventually I'll need my app to work with JSON-LD, which has property names such as "#id". I've done some googling and it seems that RxJS might have the functionality I'm looking for but I'm not sure where to start to interupt the automatic binding from some JSON data straight into my angular2 classes, and instead be able to look up something that's labeled as "#id" in the json and set the value of SomeClass.id
Current code that automatically produces an array of a Person class:
getPeople(){
return this._http.get(this._peopleUrl)
.map(response => <Person[]> response.json().data)
.do(data => console.log(data)) //debug to console
.catch(this.handleError);
}
Fine for JSON like this:
[
{
"id":"1",
"name":"tessa"
},
{
"id":"2",
"name":"jacob"
}
]
But it would obviously fail for JSON like this:
[
{
"#id":"1",
"name":"tessa"
},
{
"#id":"2",
"name":"jacob"
}
]
My class is simple (at the moment :-) )
export class Person {
constructor(
public id:number,
public name:string,
){}
}
Can anyone point me in the right direction for some documentation/examples of mapping complex/tricky json to the classes in angular2?
I found the answer in Option 4 of the answer given here: How do I initialize a typescript object with a JSON object
Hope this helps if anyone lands on this page looking for the same thing!