Creating an angular dart component dynamically - dart

I'm trying to create an angular dart component dynamically. I know it's not a best practice but I have to because of how my angular widgets are being inserted.
I based my work off of:
How to add a component programatically in Angular.Dart?
The code samples on longer work because of changes in the Angular Dart library.
I got this code to work but it's inconsistent. The solution was the Timer.run() to fire the scope.apply. The problem with that is:
It stinks to make a call like that and would perform terribly with lots of components
It seems to work randomly. Most of the time it does but occasionally it doesn't do the {{foo}} replacements
void main() {
IBMModule module = new IBMModule();
AngularModule angularModule = new AngularModule();
Injector injector = applicationFactory()
.addModule(module)
.run();
AppComponent appComponent = injector.get(AppComponent);
appComponent.addElement("<brazos-input-string label='test'/>");
}
class MyValidator implements NodeValidator {
bool allowsElement(Element element) {
return true;
}
bool allowsAttribute(Element element, String attributeName, String value) {
return true;
}
}
#Injectable()
class AppComponent {
NodeValidator validator;
Compiler _compiler;
DirectiveInjector _directiveInjector;
DirectiveMap _directiveMap;
NodeTreeSanitizer _nodeTreeSanitizer;
Injector _appInjector;
Scope _scope;
AppComponent(this._directiveInjector, this._compiler, this._directiveMap, this._nodeTreeSanitizer, this._appInjector, this._scope) {
validator = new MyValidator();
}
void addElement(String elementHTML) {
DivElement container = querySelector("#container");
DivElement inner = new DivElement();
container.append(inner);
Element element = new Element.html(elementHTML, validator: validator);
// inner.setInnerHtml(elementHTML, validator: validator);
ViewFactory viewFactory = _compiler.call([element], _directiveMap);
if (_scope != null) {
Scope childScope = _scope.createProtoChild();
View newView = viewFactory.call(childScope, _directiveInjector);
newView.nodes.forEach((node) => inner.append(node));
Timer.run(() => childScope.apply());
} else {
print("scope is null");
}
}
}
class IBMModule extends Module {
IBMModule() {
bind(BrazosInputStringComponent);
bind(BrazosTextAreaComponent);
bind(BrazosButtonComponent);
bind(ProcessDataProvider, toImplementation: ActivitiDataProvider);
bind(AppComponent);
}
}

Related

Load generic angular dynamic component

I am trying to make a dynamic component for the popup to create view and edit page for different content. I have made a popup component in which I want to pass a new component name and title of the page. However, I am not getting new component data in the popup component. Please have a look at code, if you need any more detail, please ask. Thanks in Advance.
I have tried to inject service in another component and it gets data on button click, but in the popup component, I am not getting data. For now, I am doing just console.log data to popup.component.ts file but there is no result in console.log.
popup.service.ts
export class PopupService {
isShowing = false;
private popup = new Subject();
loadingPopup = new Subject();
outputEmitter = new Subject();
popupContent: any = {
isShowing: false,
content: null,
contentParams: PopupModel
}
constructor() { }
public getPopup() {
return this.popup;
}
public showLoading(isLoading: boolean = true) {
this.loadingPopup.next(isLoading);
}
public create(component: any, parameters?: PopupModel): any {
this.showLoading(true);
this.popupContent.isShowing = true;
this.popupContent.content = component;
this.popupContent.contentParams = parameters;
this.popup.next(this.popupContent);
console.log(this.popupContent)
}
}
Popupcomponent.ts
export class PopupComponent implements OnInit, OnDestroy {
public popupObservable: Subscription;
constructor(private popupService: PopupService) { }
ngOnInit() {
this.popupObservable = this.popupService.getPopup().subscribe((popupContent: any) => {
console.log(popupContent)
//code here to use createDynamicComponent method }
}
private createDynamicComponent(component: Type<any>): void {
//code here using ComponentFactoryResolver and ViewContainerRef to create dynamic component
}
ngOnDestroy() {
if (this.popupObservable && !this.popupObservable.closed) {
this.popupObservable.unsubscribe();
}
}
}
This is the code where the dynamic component is being called and the popup should be created.
Component.ts
AddRecord(){
this.popupService.create( NewRecordComponent, {
title: 'Add',
emitterMethod: 'saved'
})
}
component.html
<button (click)="AddRecord()">Add</button>
You are not emitting a value from your subject anywhere.
You would need to call popup.next(popupContent).
However, I don't think you have the right model here.
If you're not doing any async calls (api, filesystem etc..) in your getPopup method, then just return the popupContent directly
public getPopup() : {} {
return this.popupContent;
}
You should also define an interface for your popupContent somewhere so you can import it and use a strong interface to avoid runtime errors.
e.g.
export interface IPopupContent {
isShowing: boolean;
content: string;
contentParams: PopupModel;
}
Also, don't expose subjects directly, rather expose an observable linked to the subject.
See When to use asObservable() in rxjs?

Implement dependency injection in background services in Xamarin Forms using Prism

I am making use of Prism in my xamarin forms project.I was able to use dependency injection(constructor injection) in my View Model without any problems.I am also making use of background services to push long running tasks in the background.How do I inject dependency in my Background services?When I try to pass the interface object as a paramater to the constructor(SyncingBackgroundingCode) ,the object(SqliteService) is null.I have registered and resolved the objects in the dependency injection container.
How to handle this case?Can anybody provide an example or link to implement this scenario?
This is the piece of code where im trying to implement dependency injection.
This is in Droid :-
public class AndroidSyncBackgroundService : Service
{
CancellationTokenSource _cts;
public override IBinder OnBind (Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand (Intent intent, StartCommandFlags flags, int startId)
{
_cts = new CancellationTokenSource ();
Task.Run (() => {
try {
//INVOKE THE SHARED CODE
var oBackground = new SyncingBackgroundingCode();
oBackground.RunBackgroundingCode(_cts.Token).Wait();
}
catch (OperationCanceledException)
{
}
finally {
if (_cts.IsCancellationRequested)
{
var message = new CancelledTask();
Device.BeginInvokeOnMainThread (
() => MessagingCenter.Send(message, "CancelledTask")
);
}
}
}, _cts.Token);
return StartCommandResult.Sticky;
}
public override void OnDestroy ()
{
if (_cts != null) {
_cts.Token.ThrowIfCancellationRequested ();
_cts.Cancel ();
}
base.OnDestroy ();
}
}
This is in PCL:-
public class SyncingBackgroundingCode
{
public SQLiteConnection _sqlconnection;
SqliteCalls oSQLite = new SqliteCalls();
ISqliteService _SqliteService;
public SyncingBackgroundingCode(ISqliteService SqliteService)
{
//object is null
}
public async Task RunBackgroundingCode(CancellationToken token)
{
DependencyService.Get<ISQLite>().GetConnection();
await Task.Run (async () => {
token.ThrowIfCancellationRequested();
if (App.oSqliteCallsMainLH != null)
{
App.bRunningBackgroundTask = true;
oSQLite = App.oSqliteCallsMainLH;
await Task.Run(async () =>
{
await Task.Delay(1);
oSQLite.ftnSaveOnlineModeXMLFormat("Offline", 0);
oSQLite.SyncEmployeeTableData();
oSQLite.SaveOfflineAppCommentData();
oSQLite.SaveOfflineAdditionToFlowData();
await Task.Delay(500);
var msgStopSyncBackgroundingTask = new StopSyncBackgroundingTask();
MessagingCenter.Send(msgStopSyncBackgroundingTask, "StopSyncBackgroundingTask");
});
}
}, token);
}
}
Unfortunately Xamarin and Xamarin Forms don't give frameworks like Prism anywhere to tie into to handle IoC scenarios. There are a couple of ways you can handle this though.
First the Container is a public property on the PrismApplication in your background service you could do something like:
public class FooBackgroundService
{
private App _app => (App)Xamarin.Forms.Application.Current;
private void DoFoo()
{
var sqlite = _app.Container.Resolve<ISQLite>();
}
}
Another slightly more involved way would be to use the ServiceLocator pattern. You might have something like the following:
public static class Locator
{
private static Func<Type, object> _resolver;
public static T ResolveService<T>() =>
(T)_resolver?.Invoke(typeof(T));
public static void SetResolver(Func<Type, object> resolver) =>
_resolver = resolver;
}
In your app you would then simply set the resolver. Prism actually does something similar to this with the ViewModel locator, which then allows it to inject the correct instance of the NavigationService.
public class App : PrismApplication
{
protected override void OnInitialized()
{
SetServiceLocator();
NavigationService.NavigateAsync("MainPage");
}
protected override void RegisterTypes()
{
// RegisterTypes
}
private void SetServiceLocator()
{
Locator.SetResolver(type => Container.Resolve(type, true));
}
}
Finally your service would simply reference the Service Locator like:
public class BarBackgroundService
{
public void DoBar()
{
var sqlite = Locator.ResolveService<ISQLite>();
// do foo
}
}

Angular 2 Custom Form Component: Provide a markTouched method

I have a custom form component that implements ControlValueAccessor. This component has an internal property touched.
export class BmInputComponent implements ControlValueAccessor, Validator {
private onTouchedCallback: () => {};
private touched: boolean = false;
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
onBlur() {
this.touched = true;
this.onTouchedCallback();
}
}
I need to implement a method like
markTouched() {
this.touched = true;
}
That could be called by the user of the component when markAsTouched is executed in the formControl: customInputControl.markAsTouched()
I cannot find an angular-way to do this.
#Edit:
Tried to inject the NgControl:
#Component({
selector: 'bm-input',
templateUrl: './bm-input.component.html',
encapsulation: ViewEncapsulation.None,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BmInputComponent),
multi: true
}
]
})
export class BmInputComponent implements ControlValueAccessor, Validator {
private onTouchedCallback: () => {};
private touched: boolean = false;
constructor(#Self() #Optional() public _formControl: NgControl) {
this._viewDate = new Date();
if (this._formControl) {
this._formControl.valueAccessor = this;
this._formControl.statusChanges.subscribe(this.markTouched);
}
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
onBlur() {
this.touched = true;
this.onTouchedCallback();
}
markTouched() {
if(this._formControl.touched)
this.touched = true;
}
}
But I am getting Cannot instantiate cyclic dependency! NgControl when the component is invoked with a formControl.
Have you tried #SkipSelf() instead of #Self()?
You could try this:
constructor(private injector: Injector) {
}
ngDoCheck() {
let ngControl = this.injector.get(NgControl);
if (! ngControl.control) {
return;
}
this.touched = ngControl.control.touched;
}
The circular dependency is caused by having both the NG_VALUE_ACCESSOR in your #Component(...) providers, and injecting NgControl in the constructor. These are mutually exclusive.
See the example in the NG material documentation here: https://material.angular.io/guide/creating-a-custom-form-field-control#ngcontrol

How do I add a valid default constructor to a dart polymer class?

The following code defines the Polymer element
What do I need as a valid default constructor for this class?
My question is what is needed for a proper constructor
import 'package:polymer/polymer.dart';
import 'lib/NPIDefs.dart';
import 'dart:html';
/**
* A Polymer click counter element.
*/
#CustomTag('detail-panel')
class NPIDetailPanel extends PolymerElement {
#published #observable NPIRecord record;
#observable String detailPanelICON = "unfold-less";
NPIDetailPanel.created() : super.created() {
}
setValue(NPIRecord npiRec) {
record = npiRec;
}
void dremoveDetailPanel() {
Element e;
e = shadowRoot.querySelector('#dpanel');
if(e != null) {
e.remove();
}
}
The code below gets a The class 'NPIDetailPanel' does not have a default constructor error
Please show how to do a default constructor
in the definition of the class
void addDetailPanel(Event e) {
NPIDetailPanel e1;
e1 = new NPIDetailPanel();
}
/* How do I add a proper default constructor? */
You can create a new instance of a Polymer element using new Element.tag('some-tag');
Just add a factory constructor that contains this to your Polymer element class.
#CustomTag('detail-panel')
class NPIDetailPanel extends PolymerElement {
factory NPIDetailPanel NPIDetailPanel() => new Element.tag('detail-panel'); // <== added
#published #observable NPIRecord record;
#observable String detailPanelICON = "unfold-less";
NPIDetailPanel.created() : super.created() {
}
setValue(NPIRecord npiRec) {
record = npiRec;
}
void dremoveDetailPanel() {
Element e;
e = shadowRoot.querySelector('#dpanel');
if(e != null) {
e.remove();
}
}
}
see also Instantiating polymer element via dart code

How to add a component programmatically in Angular.Dart?

I would like to dynamically build a component tree basing on some information received from AJAX calls.
How to programmatically add a component to the DOM from inside of other component? I have <outer-comp> and I would like, basing on some logic, insert an <inner-comp>. The following code just inserts the elements <inner-comp></inner-comp> to the DOM, and not actual <inner-comp> representation.
#NgComponent(
selector: 'outer-comp',
templateUrl: 'view/outer_component.html',
cssUrl: 'view/outer_component.css',
publishAs: 'outer'
)
class AppComponent extends NgShadowRootAware {
void onShadowRoot(ShadowRoot shadowRoot) {
DivElement inner = shadowRoot.querySelector("#inner");
inner.appendHtml("<inner-comp></inner-comp>");
}
}
Update:
I managed to render the inner component correctly in the following way, but I'm still not sure if this is the proper way:
class AppComponent extends NgShadowRootAware {
Compiler compiler;
Injector injector;
AppComponent(this.compiler, this.injector);
void onShadowRoot(ShadowRoot shadowRoot) {
DivElement inner = shadowRoot.querySelector("#inner");
inner.appendHtml("<inner-comp></inner-comp>");
BlockFactory template = compiler(inner.nodes);
var block = template(injector);
inner.replaceWith(block.elements[0]);
}
}
The API has changed in AngularDart 0.9.9:
BlockFactory now is ViewFactory
scope.$new now seems to be scope.createChild(scope.context)
injector.createChild(modules) now requires a list of modules (instead of a single one)
AngularDart 0.10.0 introduces these changes:
NgShadowRootAware not is ShadowRootAware
..value() now is ..bind(., toValue: .)
So the code of pavelgj now looks like so:
class AppComponent extends ShadowRootAware {
Compiler compiler;
Injector injector;
Scope scope;
DirectiveMap directives;
AppComponent(this.compiler, this.injector, this.scope, this.directives);
void onShadowRoot(ShadowRoot shadowRoot) {
DivElement inner = shadowRoot.querySelector("#inner");
inner.appendHtml("<inner-comp></inner-comp>");
ViewFactory template = compiler([inner], directives);
Scope childScope = scope.createChild(scope.context);
Injector childInjector =
injector.createChild([new Module()..bind(Scope, toValue: childScope)]);
template(childInjector, [inner]);
}
}
This would be a proper use of the block API.
class AppComponent extends NgShadowRootAware {
Compiler compiler;
Injector injector;
Scope scope;
DirectiveMap directives;
AppComponent(this.compiler, this.injector, this.scope, this.directives);
void onShadowRoot(ShadowRoot shadowRoot) {
DivElement inner = shadowRoot.querySelector("#inner");
inner.appendHtml("<inner-comp></inner-comp>");
BlockFactory template = compiler([inner], directives);
Scope childScope = scope.$new();
Injector childInjector =
injector.createChild(new Module()..value(Scope, childScope));
template(childInjector, [inner]);
}
}
Also, if you ever need to recompile the inner template make sure you do childScope.$destroy() on the previous childScope.
The above code samples on longer work because of changes in the Angular Dart library. Specifically ViewFactory.call which no longer takes an injector but takes a Scope and a DirectiveInjector. I've tried adapting what's above and I get very close. The component shows up but none of the bindings are replaced (I see {{cmp.value}} for example.
Here's the code I'm using. I think the issue here is that DirectiveInjector is coming in as null.
void main() {
IBMModule module = new IBMModule();
AngularModule angularModule = new AngularModule();
Injector injector = applicationFactory()
.addModule(module)
.run();
AppComponent appComponent = injector.get(AppComponent);
appComponent.addElement("<brazos-input-string label='test'/>");
}
#Injectable()
class AppComponent {
NodeValidator validator;
Compiler _compiler;
DirectiveInjector _injector;
DirectiveMap _directiveMap;
NodeTreeSanitizer _nodeTreeSanitizer;
Scope _scope;
AppComponent(this._injector, this._compiler, this._directiveMap, this._scope, this._nodeTreeSanitizer) {
validator = new NodeValidatorBuilder.common()
..allowCustomElement("BRAZOS-INPUT-STRING")
..allowHtml5()
..allowTemplating();
}
void addElement(String elementHTML) {
DivElement container = querySelector("#container");
DivElement inner = new DivElement();
inner.setInnerHtml(elementHTML, validator: validator);
ViewFactory viewFactory = _compiler.call([inner], _directiveMap);
Scope childScope = _scope.createChild(new PrototypeMap(_scope.context));
if (_injector == null) {
print("injector is null");
}
View newView = viewFactory.call(childScope, _injector);
container.append(inner);
newView.nodes.forEach((node) => inner.append(node));
}
}
class IBMModule extends Module {
IBMModule() {
bind(BrazosInputStringComponent);
bind(BrazosTextAreaComponent);
bind(BrazosButtonComponent);
bind(ProcessDataProvider, toImplementation: ActivitiDataProvider);
bind(AppComponent);
}
}
I did finally get this to work but was not happy with having to add a timer:
#Injectable()
class AppComponent{
NodeValidator validator;
Compiler _compiler;
DirectiveInjector _directiveInjector;
DirectiveMap _directiveMap;
NodeTreeSanitizer _nodeTreeSanitizer;
Injector _appInjector;
Scope _scope;
AppComponent(this._directiveInjector, this._compiler, this._directiveMap, this._nodeTreeSanitizer, this._appInjector, this._scope) {
validator = new MyValidator();
}
void addElement(String id, String elementHTML) {
DivElement container = querySelector(id);
DivElement inner = new DivElement();
container.append(inner);
Element element = new Element.html(elementHTML, validator: validator);
ViewFactory viewFactory = _compiler.call([element], _directiveMap);
if (_scope != null) {
Scope childScope = _scope.createProtoChild();
View newView = viewFactory.call(childScope, _directiveInjector);
newView.nodes.forEach((node) => inner.append(node));
Timer.run(() => childScope.apply());
} else {
print("scope is null");
}
}
}
EDIT
The package http://pub.dartlang.org/packages/bwu_angular contains this decorator/directive as bwu-safe-html
------
I use a custom directive for that
#NgDirective(
selector: '[my-bind-html]'
)
class MyBindHtmlDirective {
static dom.NodeValidator validator;
dom.Element _element;
Compiler _compiler;
Injector _injector;
DirectiveMap _directiveMap;
MyBindHtmlDirective(this._element, this._injector, this._compiler, this._directiveMap) {
validator = new dom.NodeValidatorBuilder.common()
..allowHtml5()
..allowImages();
}
#NgOneWay('my-bind-html')
set value(value) {
if(value == null) {
_element.nodes.clear();
return;
}
_element.setInnerHtml((value == null ? '' : value.toString()),
validator: validator);
if(value != null) {
_compiler(_element.childNodes, _directiveMap)(_injector, _element.childNodes);
}
}
}
It can be used like
my-bind-html='ctrl.somehtml'
Angular issue
I created an issue to include this functionality into Angulars ng-bind-html https://github.com/angular/angular.dart/issues/742 (declined)

Resources