When I create a component in Angular.dart like
library main;
import 'package:angular/angular.dart';
import 'package:di/di.dart';
class Item {
String name;
Item(this.name);
}
#NgComponent(
selector: 'my-component',
publishAs: 'ctrl',
applyAuthorStyles: true,
template: '''<div ng-repeat="value in ctrl.values"><span>{{value.name}}</span> - <content><content></div>'''
)
class MyComponent {
List<Item> values = [new Item('1'), new Item('2'), new Item('3'), new Item('4')];
MyComponent() {
print('MyComponent');
}
}
class MyAppModule extends Module {
MyAppModule() {
type(MyComponent);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
and use it like
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="utf-8">
</head>
<body>
<h3>Repeat</h3>
<my-component>
<div>some provided content to repeat</div>
</my-component>
<script type="application/dart" src="index.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
I get
I know the <content> tag isn't working that way in web components.
But is there any other way, some manipulation I can do in my component, to get the <div> provided as child element repeated?
I solved it like
Code of <my-component>
#NgComponent(
selector: 'my-component',
publishAs: 'ctrl',
template: '''<div ng-repeat="value in ctrl.values"><span ng-bind-nodes="ctrl.nodes"></span><span>something hardcoded: {{value.name}}</span></div><content id='content'></content>'''
)
class MyComponent extends NgShadowRootAware {
List<Item> values = [new Item('1'), new Item('2'), new Item('3'), new Item('4')];
List<dom.Node> nodes = new List<dom.Node>();
MyComponent();
#override
void onShadowRoot(dom.ShadowRoot shadowRoot) {
nodes.addAll((shadowRoot.querySelector('#content') as dom.ContentElement).getDistributedNodes());
//nodes.forEach((n) => print(n));
nodes.forEach((n) => n.remove());
}
}
The component removes it's child nodes and provides them in the field nodes
the directive ng-bind-nodes
adds the nodes to the element where it is applied
#NgDirective(
selector: '[ng-bind-nodes]',
publishAs: 'ctrlx' // conflicts with my-component
)
class NgBindNodesDirective {
dom.Element _element;
MyComponent _myComponent;
Scope _scope;
Compiler _compile;
Injector _injector;
NgBindNodesDirective(this._element, this._myComponent, this._scope, this._compile, this._injector);
#NgOneWay('ng-bind-nodes') set nodes(var nodes) {
print(nodes);
if(nodes == null) {
return;
}
_element.nodes.clear();
nodes.forEach((dom.Node node) {
_element.nodes.add(node.clone(true));
});
BlockFactory template = _compile(_element.nodes);
Block block = template(_injector, _element.nodes);
}
}
I don't have an answer, and I can't test my suggestion right now, but try injecting the element, compiler, scope and blockfactory in MyComponent:
Element element;
Compiler compiler;
Injector injector;
Scope scope;
MyComponent(this.element, this.compiler, this.injector, this.scope) {
}
You can access the div as child of 'element'.
Then you don't use template of NgComponent, but instead build your own template from a string, insert the child and compile it:
String template = '''<div ng-repeat="value in ctrl.values"><span>{{value.name}}</span> - <div id="inner"><div></div>''';
void onShadowRoot(ShadowRoot shadowRoot) {
List<DivElement> children = element.children;
shadowRoot.appendHtml(template);
DivElement inner = shadowRoot.querySelector('#inner');
inner.children.add(children);
BlockFactory fact = compiler([shadowRoot]);
Scope childScope = scope.$new();
Injector childInjector =
injector.createChild([new Module()
..value(Scope, childScope)]);
fact(childInjector, children);
}
Maybe it gives you the right direction.
Related
I have this minimal code for an Angular Dart component, I've even written it in the same file, but I cant get it to work.
import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
void main() {
applicationFactory().addModule(new TT()).run();
}
class TT extends Module
{
TT ()
{
bind(SimpleString);
}
}
#Component(
selector: 'simplestring',
publishAs: 'cmp',
template: '<div> {{cmp.str}} </div>'
)
class SimpleString
{
#NgAttr('str')
String str;
}
Html body
<body>
<simplestring str='hola'></simplestring>
</body>
This should show "hola", but nothing is happening.
publishAs:is deprecated and not interpreted any more since AngularDart 1.0
Use the property name directly:
#Component(
selector: 'simplestring',
template: '<div> {{str}} </div>'
)
class SimpleString
{
#NgAttr('str')
String str;
}
To call ngBootstrap I used
void main() {
initPolymer()
.run(() {
ngBootstrap(module: new AppModule());
});
}
Since polymer 0.10.0-pre.8 this seems not possible anymore:
Dartium currently only allows a single Dart script tag per application, and in the future it will run them in separtate isolates. To prepare for this all the following script tags need to be updated to use the mime-type "application/dart;component=1" instead of "application/dart":
⪪script type="application/dart" src="main.dart"></script>
Only one Dart script tag allowed per document
But my main is not a component - it is a regular main!!!
Was easier than thought.
index.html:
<head>
<script type='application/dart;component=1' src='main.dart'></script>
</head>
main.dart:
import 'package:polymer/polymer.dart';
import 'package:angular/angular.dart';
import 'package:angular/angular_dynamic.dart';
// HACK until we fix code gen size. This doesn't really fix it,
// just makes it better.
#MirrorsUsed(override: '*')
import 'dart:mirrors';
void myRouteInitializer(Router router, RouteViewFactory views) {
views.configure({
'hello': ngRoute(
path: '/hello',
enter: views('views/hello.html')),
'goodbye': ngRoute(
path: '/hellopolymer/:callerID',
enter: views('views/hello-polymer.html'))
});
}
#NgController( selector: '[webapp-sample]', publishAs: 'ctrl')
class MyControler {
final Repository _repository;
MyControler(final RouteProvider routeProvider,this._repository) {
final int value = routeProvider.parameters["callerID"];
if(value != null && value != null) {
_repository.value = value;
}
}
int get value => _repository.value;
}
class Repository {
int value = 0;
}
class AppModule extends Module {
AppModule() {
value(RouteInitializerFn, myRouteInitializer);
value(Repository,new Repository());
type(MyControler);
factory(NgRoutingUsePushState, (_) => new NgRoutingUsePushState.value(false));
}
}
#initMethod
void init() {
dynamicApplication().addModule(new AppModule()).run();
}
I'am starting to learn Dart/AngularDart and i'am trying to display a simple component following the tutorial in https://angulardart.org/ , my problem is that i got a blank page nothing is displayed.
Here is my code:
web/nasiha.dart
import 'dart:html';
import 'package:angular/angular.dart';
import 'components/post/post.dart';
import 'dart:mirrors';
class MyAppModule extends Module {
MyAppModule() {
type(PostComponent);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
web/nasiha.html
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="utf-8">
<title>Nasiha</title>
<link rel="stylesheet" href="css/nasiha.css">
</head>
<body>
<post></post>
<script src="packages/shadow_dom/shadow_dom.min.js"></script>
<script type="application/dart" src="nasiha.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
web/components/post/post.dart
import 'package:angular/angular.dart';
#NgComponent(
selector: 'post',
templateUrl:'components/post/post.html',
cssUrl: 'components/post/post.css',
publishAs: 'cmp_post'
)
class PostComponent {
String text= "This is a simple text to show";
String userName = "test";
DateTime date= new DateTime.now();
PostComponent(String text, String userName, DateTime date){
this.text = text;
this.userName = userName;
this.date = date;
}
String getText(){
return this.text;
}
void setText(String text){
this.text = text;
}
DateTime getDate(){
return this.date;
}
void setDate(DateTime date){
this.date = date;
}
String getUserName(){
return this.userName;
}
void setUserName(String userName){
this.userName = userName;
}
}
web/components/post/post.html
<div>
<p ng-model="cmp_post.post_text">
{{cmp_post.text}}
</p>
<div ng-model="cmp_post.post_date">
{{cmp_post.date}}
</div>
<div ng-model="cmp_post.post_username">
{{cmp_post.userName}}
</div>
</div>
You should execute pub upgrade from the context menu on pubspec.yaml.
The ng-model attributes in web/components/post/post.html are redundant.
PostComponent(String text, String userName, DateTime date){
this code is invalid.
Either you register a class in your module that can be injected to
the constructor or you use annotations to be able to inject primitive
types like String, int, double, ... (If you want to know how inject primitive types or using annotations for injection see How can I Dependency Inject based on type and name, with AngularDart?
I have the following component
#NgComponent(selector: 'foo',
template: '<div>foo component</div>')
class FooComponent {
void doSomething();
}
it's used as follows:
<html>
<head></head>
<body>
<foo ng-click="ctrl.doSomething()"></foo> // This is wrong
</body>
</html>
How do I actually execute a function inside an NgComponent?
Good question
What I come up with (probably not exactly what you are looking for):
#NgController(
selector: '[do-something]',
publishAs: 'ctrl'
)
class DoSomething {
FooComponent _foo;
DoSomething(this._foo);
void clickHandler(e) {
_foo.doSomething();
}
}
.
<foo do-something ng-click="ctrl.doSomething()"></foo>
Here is a one poor solution, but if there is no other solution, then you can use this.
EDIT: I updated this solution completely. With this example one can define what event component recognizes and to what function each event is attached.
html:
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="utf-8">
<title>Foo</title>
<link rel="stylesheet" href="ok_comp.css">
</head>
<body>
<foo click="test()" doubleclick="test2()"></foo>
<foo click="test2()"></foo>
<script type="application/dart" src="ok_comp.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
comp.dart:
import 'dart:html';
import 'package:angular/angular.dart';
#NgComponent(
selector: 'foo',
template: '<div>foo</div>'
)
class FooComp extends NgAttachAware {
#NgAttr('click')
var click;
#NgAttr('doubleclick')
var doubleclick;
Element element;
var func;
FooComp(this.element){
}
attach(){
attachFunc("click", click);
attachFunc("doubleclick", doubleclick);
}
void attachFunc(String listener, String funcName){
switch (funcName) {
case 'test()':
func = test;
break;
case 'test2()':
func = test2;
break;
}
switch (listener) {
case 'click':
element.onClick.listen(func);
break;
case 'doubleclick':
element.onDoubleClick.listen(func);
break;
}
}
test(MouseEvent event){
print ("test");
}
test2(MouseEvent event){
print ("test2");
}
}
class MyAppModule extends Module {
MyAppModule() {
type(FooComp);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
You can add an event listener to component. Here is an example:
html:
<foo></foo>
comp.dart:
#NgComponent(selector: 'foo',
template: '<div>foo component</div>')
class FooComponent {
FooComponent(Element elem){
elem.onClick.listen(doSomething);
}
void doSomething(MouseEvent event){
print("click");
}
}
This question keeps bothering me and I had to test it a little bit more. In the following example a component has multiple functions and multiple build-in ng-directives. You can define which functions are related to which ng-directives through component's attributes.
html:
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="utf-8">
<title>Foo</title>
<link rel="stylesheet" href="ok_comp.css">
</head>
<body>
<foo2 click="test" doubleclick="test2"></foo2>
<foo2 click="test2"></foo2>
<script type="application/dart" src="ok_comp.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
comp.dart:
import 'dart:html';
import 'package:angular/angular.dart';
#NgComponent(
selector: 'foo2',
template: '<div ng-click="cmp.ngClick()" ng-doubleclick="cmp.ngDoubleClick()">foo2</div>',
publishAs: 'cmp'
)
class Foo2Comp extends NgAttachAware {
#NgAttr('click')
var strClick;
#NgAttr('doubleclick')
var strDoubleclick;
var ngClick;
var ngDoubleClick;
Foo2Comp(){
}
attach(){
ngClick = redirectFunc(strClick);
ngDoubleClick = redirectFunc(strDoubleclick);
}
redirectFunc(String funcName){
var ng;
switch (funcName) {
case 'test':
ng = test;
break;
case 'test2':
ng = test2;
break;
default:
ng = empty;
break;
}
return ng;
}
empty(){
print ("empty");
}
test(){
print ("test");
}
test2(){
print ("test2");
}
}
class MyAppModule extends Module {
MyAppModule() {
type(Foo2Comp);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
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)