I went through this issue while working on the ScheduleJS framework. At some point I am provided with a HTMLCanvasElement which I want to replace with a dynamically generated component programatically. To do so, and to keep the code as clean as possible, I'd like to create my own Angular components at runtime and use the HTMLCanvasElement.replaceWith(component) method from the provided HTMLCanvasElement replacing the canvas with the dynamically created component.
Here is the Angular service I came up with, which does the job the way I expected:
import {ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, Injector, Type} from "#angular/core";
import {ReplacementComponent} from "xxx"; // This is a higher order type of Component
#Injectable({providedIn: "root"})
export class DynamicComponentGenerator {
// Attributes
private _components: Map<string, ComponentRef<ReplacementComponent>> = new Map();
private _currentKey: number = 0;
// Constructor
constructor(private _appRef: ApplicationRef,
private _resolver: ComponentFactoryResolver,
private _injector: Injector) { }
// Methods
create(componentType: Type<ReplacementComponent>): ComponentRef<ReplacementComponent> {
const componentRef = componentType instanceof ComponentRef
? componentType
: this._resolver.resolveComponentFactory(componentType).create(this._injector);
this._appRef.attachView(componentRef.hostView);
this._components.set(`${this._currentKey}`, componentRef);
componentRef.instance.key = `${this._currentKey}`;
this._currentKey += 1;
return componentRef;
}
remove(componentKey: string): void {
const componentRef = this._components.get(componentKey);
if (componentRef) {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(componentKey);
}
}
clear(): void {
this._components.forEach((componentRef, key) => {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(key);
});
this._currentKey = 0;
}
}
So basically this service lets me create a component with .create(ComponentClass) remove it by providing the component key .remove(key) and clear() to remove all the components.
My issues are the following:
The ComponentFactoryResolver class is deprecated, should I use it anyways?
Could not manage to use the newer API to create unattached components (not able to have access to an Angular hostView)
Is there a better way to do this?
Thank you for reading me.
You could try using new createComponent function:
import { createComponent, ... } from "#angular/core";
const componentRef =
createComponent(componentType, { environmentInjector: this._appRef.injector});
this._appRef.attachView(componentRef.hostView);
Related
I am trying to write a small browser game in Typescript. So to start, I have a Scenario class that would need to load characters' dialogues based on scenario names.
Here is how the Scenario class looks like:
export class Scenario extends Entity{
public characters: Character[] = [];
constructor(
public readonly scenarionName: string,
public readonly startDialogueId: string
) {
super();
}
public awake() {
this.loadCharacters();
}
private loadCharacters(){
this.characters = [];
// FIXME how can I dynamically load dialogues based on scenario name?
}
}
My idea was to have dialogues stored in JSON files, which would then be loaded based on the scenario name, which would also be a package name. Here how it looks:
In this case, I want to dynamically load 'npc.json' and 'player.json' into Scenario class based on the scenario name which in this case is a 'test'. In the future, there would be more scenarios and I would like to avoid hard coding.
Can this be done in such a way and if so, how?
EDIT: the game would run in the browser.
Use fs module to get the file names in a directory and read the contents of the files.
import fs from('fs');
export class Scenario extends Entity{
public characters: Character[] = [];
constructor(
public readonly scenarionName: string,
public readonly startDialogueId: string,
scenarioPath: string
) {
super();
}
public awake() {
this.loadCharacters();
}
private loadCharacters(){
this.characters = [];
const dialoguesDir = `${this.scenarioPath}/${this.scenarioName}/dialogues/`;
let dialogueContent;
fs.readdirSync(dialoguesDir).forEach(filePath => {
dialogueContent = fs.readFileSync(`${dialoguesDir}${filePath}`).toString();
// Do something with contents
});
}
}
I am trying to send data between two unrelated components. I am trying to utilize Event Emitter Output and Address Service.
How to get the first Address Dropdown Event Emitter, to Pass Data to the Service? The service can then send data to the Receiver.
export class AddressDropdownComponent implements OnInit {
addresses: any[] = [];
#Input() addressDefaultItem: AddressDto;
#Input() selectedAddress: any;
#Input() TxtField: string = 'addressDescription';
#Output() selectedItemOutput = new EventEmitter();
constructor(private addressService:AddressServiceProxy ) { }
ngOnInit() {
}
statusSelectedItemChanged(e) {
this.selectedAddress = e;
}
Still Working on This Address Service
export class AddressService {
private messageSource = new BehaviorSubject("default message");
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Resource: following is only for parent-child, looking for unrelated 'grandfather' or 'sibling' cases
What is the best way to use nested components in Angular 4 and calling a method of a parent to child and vice versa?
So you have a source let's say ComponentA and some component on top of it several levels let's say ComponentV.
To connect them you need first to connect them via a service. If there is only one instance of each of them you can use a singletone service (has providedIn: 'root' in #Injectable decorator). However, if you can have multiple ComponentV which contain ComponentA you need to provide this service at top-level of hierarchy. In this case ComponentV must have providers: [SomeService] to create a new service instance when ComponentV is created.
Then you define some prop in SomeService to share data.
For example you end up with
// SomeService contents
...
// create a source
private dataSource$ = new Subject();
// Expose as observable so it's value can be changed
// only via exposed method (dataChanged)
data$ = this.dataSource$.asObservable();
// public API to pass new data
dataChanged(data) {
this.dataSource$.next(data);
}
...
Then you inject this service in ComponentA and define a function to emit data change.
// ComponentA contents
...
constructor(private readonly someService: SomeService) {}
onDataChange(data) {
// here we notify about data change
this.someService.dataChanged(data);
}
...
Then you need to subscribe to the observable in top level component
// CompoentV contents
...
constructor(private readonly someService: SomeService) {
// here we subsribe to changes and use whatever handler we need
this.someService.data$.subscribe(data => {
// some logic goes here or pass this data to a method
});
}
...
This allows one to share some state or events between unrelated or loosely related components.
I have a model class with a property that returns a value by calling a method, but when i try to bind that property, there is result on the page, but also no error occuring.
export class TestClass {
testProperty: string = this.getString();
getString() {
return 'hello';
}
}
in html:
{{model.testProperty}}
Does Typescript / Angular not support this? What is the common way to do it?
This is a simple enough class. What you could do is initialize testProperty as null or by a default value and in ngOnInit() assign the returned value from the function.
import { Component, OnInit } from '#angular/core';
export class TestClass implements OnInit {
testProperty: string = null;
ngOnInut() {
this.testProperty = this.getString();
}
getString() {
return 'hello';
}
}
The ngOnInit() is a lifecycle hooks and runs when the component is initialized.
public get testProperty(): string {
return 'hello'
}
If you use the 'get' after the public, it allow the function to be called as it would be a normal variable. I think you shouldnt even need the 'model.'
I have a simple class that is written in ES6
class MyClass {
constructor() {
this.firstVar = 'a';
this.secondVar = 'b';
}
get first() { return this.firstVar; }
set first(val) { this.firstVar = val; }
get second() { return this.secondVar; }
set second(val) { this.secondVar = val; }
allValues() {
return this.firstVar + this.secondVar;
}
}
export { MyClass };
I compile that code to ES5 via babel MyClass.es6 > MyClass.js and then try to use it in my existing ES5 codebase:
var t = require('./MyClass');
console.log(t.allValues());
But I get an error stating that t has no method allValues. Is what I'm trying to do possible?
Classes have to be instantiated. You are never creating an instance of MyClass. You are also importing it incorrectly. You are exporting MyClass as a named export and have to import it as such.
In its current form, you would have to do
var MyClass = require('./MyClass').MyClass;
var t = new MyClass();
console.log(t.allValues());
Or you can export it as default export:
export default MyClass;
In which case you can do
var t = require('./MyClass');
(at least with Babel)
I have a form with a search field.
When user press enter key, I use httpservice to send a query to mySQL database.
In some case (a lot) there are several record, so a new window is opening to show those record with a datagrid to let user chose the good result.
My problem is how to send selected information to the first window (with text field).
I gess that dispatch event is the way but I don't found how to use!
Can you help me to find a solution.
Thanks
If you are trying to communicate within an MDI environment I suggest that you use some kind of shared model ( aka Mediator or Presentation Model ) that keeps a contract between the desired windows.
class SelectionPM{
[Bindable]
public var selectedItem:Object;
}
Use case:
Window1 has an instance of SelectionPM, when you open Window2 you pass
SelectionPM instance to it, then update SelectionPM.selectedItem
property on changing selection in the Window2 datagrid. That will
propagate the binding chain up to Window1, where you can use the
SelectionPM.selectedItem as you wish.
Ideally you would use an IoC container for model injection but that is another story.
That might seem like a lot of work but if you keep this methodology across your app you will benefit from it.
Cheers!
Here's a set of four classes as a basis. Obviously you don't want to be doing the actual work in the constructors as below.
public class App
{
public static var eventDispatcher:EventDispatcher = new EventDispatcher();
public function App()
{
new Window1();
}
}
class AppEvent extends Event
{
public static const DATA_READY:String = "APPEVENT.DATA_READY";
public var data:Object;
public function AppEvent( type:String, data:Object )
{
super( type );
this.data = data;
}
}
class Window1
{
public function Window1()
{
App.eventDispatcher.addEventListener( AppEvent.DATA_READY, onDataReady );
...DO STUFF...
new Window2();
}
private function onDataReady( evt:AppEvent ) : void
{
...DO STUFF WITH "evt.data"....
}
}
class Window2
{
public function Window2()
{
...GET DATA FROM SERVER AND PUT IT IN "data"...
App.eventDispatcher.dispatchEvent( new AppEvent( AppEvent.DATA_READY, data ) );
}
}
</pre>