how to select light DOM in angular component - dart

I try to select everything inside the content tag in an angular component
<my-component>
<another-component></another-component>
<another-component></another-component>
<another-component></another-component>
</my-component>
my-component.html
<div>
<content></content>
</div>
my-component.dart
#Component(
selector: "my-component",
templateUrl: 'components/my-component.html',
useShadowDom: false,
publishAs: "ctrl"
)
class MyComponent {
List<Element> content; //elements of content tags
}
How can i select everything inside of and put it into my list.
Is there a special method where i can access the content?

Add a constructor to MyComponent with an argument of type Element and select the children.
class MyComponent {
List<Element> content; //elements of content tags
MyComponent(Element e) {
content = <Element>[].addAll(e.children);
// or
content = <Element>[].addAll((e.querySelector('content') as ContentElement).getDistributedNodes());
}
}
(use with caution, didn't test the code and didn't use Angular.dart for a while)

Related

How to pass data to the markup of a custom component?

I have a custom component that allows for editing a user. It displays a dialog which can be fed an existing user. Or not. It has the markup:
<button mat-button (click)="openUserDialog()">Edit</button>
and the controller:
#Component({
selector: 'app-user-edit',
templateUrl: './user-edit.component.html',
})
export class UserEditComponent implements OnChanges {
#Input() existingUser: User;
#Output() userEditedEvent: EventEmitter<User> = new EventEmitter<User>();
userDialogRef: MatDialogRef<UserDialogComponent>;
constructor(
private matDialog: MatDialog,
private userService: UserService
) { }
ngOnChanges() {
}
openUserDialog() {
this.userDialogRef = this.matDialog.open(UserDialogComponent, {
hasBackdrop: false,
data: {
user: this.existingUser
}
});
this.userDialogRef
.afterClosed()
.subscribe(user => {
// TODO validate the edited user
if (user) {
if (this.existingUser) {
user.id = this.existingUser.id;
this.userService.fullUpdate(user)
.subscribe(updatedUser => {
this.userEditedEvent.emit(updatedUser);
// TODO Add a hint that the user has been added
});
} else {
this.userService.add(user)
.subscribe(addedUser => {
this.userEditedEvent.emit(addedUser);
// TODO Add a hint that the user has been updated
});
}
}
});
}
}
The component is then being used in the users list page, once on top of the list to add a new user, with the markup:
<app-user-edit (userEditedEvent)="refreshList($event)"></app-user-edit>
and on each row of the list to edit the user, with the markup:
<app-user-edit [existingUser]="user" (userEditedEvent)="refreshList($event)"></app-user-edit>
The trouble is that the view displays the Edit label both to add and to edit a user.
How could I have a custom Add label on top of the list, and another Update label for each user ?
I feel like I may have overengineered the whole thing.
You can add another #Input parameter say label and pass the value of the label from the mark up.
export class UserEditComponent implements OnChanges {
#Input() existingUser: User;
#Input() label: string = "Edit" // set default to EDIT. If preferred can initialised to empty.
Mark up for ADD:
<app-user-edit (userEditedEvent)="refreshList($event)" label="ADD"></app-user-edit>
Mark up for EDIT:
<app-user-edit [existingUser]="user" (userEditedEvent)="refreshList($event)" label="EDIT"></app-user-edit>
Also, bind parameter label in view where it needs to be shown.

How to notify parent component of change and refresh view

I want to notify a parent component from a child component to update a view in the parent component. I'm using the #Output annotation to do that.
In the parent component the function "loadPosts()" is actually invoked, but the view is not updated. Does anybody know why?
What happens:
place_component contains a "list-post" directive, which displays all posts.
place_component contains a modal, to add a new post with a directive "new-post"
When a new post is saved, a message is parsed back over #output to the "new-post" directive in the modal: (doneIt)="loadPosts()"
The loadPosts() function is executed, but the "list-post" directive does not reload.
Parent Component:
place_component.dart:
#Component(
selector: 'my-place',
directives: [coreDirectives,
formDirectives,
PostNewComponent,
PostListComponent,
MaterialButtonComponent,
MaterialDialogComponent,
ModalComponent,
MaterialTabPanelComponent,
MaterialTabComponent],
templateUrl: 'place_component.html',
styleUrls: ['place_component.css'],
providers: [materialProviders]
)
class PlaceComponent implements OnActivate, OnInit {
Place place;
final PlaceService _placeService;
final Location _location;
final ChangeDetectorRef cdRef;
int _id;
bool showBasicDialog = false;
final tabLabels = const <String>[
'Posts',
'Pictures',
'Pending Invitations'
];
PlaceComponent(this._placeService, this._location, this.cdRef);
#override
Future<void> onActivate(_, RouterState current) async {
_id = paths.getId(current.parameters);
loadPosts();
}
#override
Future<void> ngOnInit() async {
print("init executed");
}
Future<void> loadPosts() async {
if (_id != null) place = await (_placeService.get(_id));
cdRef.detectChanges();
print("loaded posts $_id");
}
void goBack() => _location.back();
Future<void> save() async {
await _placeService.update(place);
goBack();
}
}
place_component.html:
<div *ngIf="place != null">
<h2>{{place.name}}</h2>
<div class="grid">
<div class="col-1-3">
<div class="module">
<material-button class="open-post-button" (trigger)="showBasicDialog = true" [disabled]="showBasicDialog" raised>
New Post
</material-button>
</div>
</div>
<div class="col-2-3">
<div class="module">
<material-tab-panel class="tab-panel" [activeTabIndex]="0">
<material-tab label="Posts">
<div class="posts">
<div class="post">
<list-posts [place]="place"></list-posts>
</div>
</div>
</material-tab>
<material-tab label="Pictures">
Pictures
</material-tab>
<material-tab label="Videos">
Videos
</material-tab>
</material-tab-panel>
<div class="divider10"></div>
</div>
</div>
</div>
</div>
<modal [visible]="showBasicDialog">
<material-dialog class="basic-dialog">
<h1 header>New Post</h1>
<div class="new-post">
<new-post (doneIt)="loadPosts()" [place]="place"></new-post>
</div>
<div footer>
<material-button autoFocus clear-size (trigger)="showBasicDialog = false" class="close-button">
Close
</material-button>
</div>
</material-dialog>
</modal>
Child Component
post_new_component.dart:
#Component(
selector: 'new-post',
directives: [coreDirectives,
formDirectives,
FileUploader,
materialInputDirectives,
MaterialButtonComponent],
templateUrl: 'post_new_component.html',
styleUrls: ['post_new_component.css'],
providers: [ClassProvider(PostService)]
)
class PostNewComponent {
final PostService _postService;
final _onDone = new StreamController.broadcast();
String postText;
Post post;
#Input()
Place place;
#Output()
Stream get doneIt => _onDone.stream;
PostNewComponent(this._postService);
Future<void> save() async {
await _postService.create(postText,place.id).then(((_) => _onDone.add(1)));
}
}
post_new_component.html:
<div class="post-new-component">
<div>
<material-input floatingLabel
multiline
rows="2"
maxRows="4"
label="Add a new post here...."
[(ngModel)]="postText"
class="post-text">
</material-input>
</div>
<div class="post-buttons">
<file-uploader class="file-uploader"></file-uploader>
<div><material-button (trigger)="save()" raised class="send-button">Post</material-button></div>
</div>
<div class="clear-float"></div>
</div>
I have now additionally also tried with an EventBus according to this example: AngularDart: How do you pass an event from a Child component to a second level parent
PlaceComponent(this._placeService, this._location, this._postEvent, this.cdRef) {
_postEvent.onEventStream.listen((int id) => loadPosts().then((_){cdRef.markForCheck();}));
}
The behaviour is exactly the same. The loadPosts function is executed, but the view is not loading.
Sometimes Angular does not trigger change detection after async call, you need to force it using ChangeDetectorRef
final ChangeDetectorRef cdRef;
PlaceComponent(this.cdRef);
Future<void> loadPosts() async {
if (_id != null) place = await (_placeService.get(_id));
////
cdRef.markForCheck();
// or
cdRef.detectChanges();
/// actually I don't know what is the best here
}
I had following setup:
Parent Component places_component
Child Component post_new_component
Child Component post_list_component
To solve my problem I had to send the event not to the Parent Component, but to the other child component. So #Output wouldn't have worked. I just hooked up the EventBus to the other child component.
So the html of the parent component in short looked like this:
...
<div><new-post [place]="place"></new-post></div>
<div><list-posts [place]="place"></list-posts></div>
...
So the child component new-post needs to notify the child component list-posts, that a new post has been added and list-posts should re-fetch all posts related to a place.
post_event.dart (Event Bus Service)
The Event Bus service is setup between the post_new_component and the post_list_component.
I'm passing an int to the Stream (int id), this is not important right now, as I only need to check, if an event has fired, you could also parse a string, an object or anything else, if you need to send data with the event.
#Injectable()
class PostEvent {
final StreamController<int> _onEventStream = new StreamController.broadcast();
Stream<int> onEventStream = null;
static final PostEvent _singleton = new PostEvent._internal();
factory PostEvent() {
return _singleton;
}
PostEvent._internal() {
onEventStream = _onEventStream.stream;
}
onEvent(int id) {
_onEventStream.add(id);
}
}
post_new_component.dart
After the post has been added, _postEvent.onEvent(1) is executed. As explained above "1" is not important, as I only want to know, if an event was fired.
#Component(
selector: 'new-post',
directives: [coreDirectives,
formDirectives,
FileUploader,
materialInputDirectives,
MaterialButtonComponent],
templateUrl: 'post_new_component.html',
styleUrls: ['post_new_component.css'],
providers: [ClassProvider(PostService), ClassProvider(PostEvent)]
)
class PostNewComponent {
final PostService _postService;
final PostEvent _postEvent;
String postText;
Post post;
#Input()
Place place;
PostNewComponent(this._postService, this._postEvent);
// Save a new post
Future<void> save() async {
// Create a new post and then fire a post event to notify the post list component to update itself.
await _postService.create(postText,place.id).then(((_) => _postEvent.onEvent(1)));
}
}
post_list_component.dart
I setup the event listener in the constructor of the component, that listens for event changes from the post-new component. Each time an event is received, I fetch all posts through the _getPosts() function.
#Component(
selector: 'list-posts',
directives: [coreDirectives, formDirectives],
templateUrl: 'post_list_component.html',
styleUrls: ['post_list_component.css'],
providers: [ClassProvider(PostService), ClassProvider(PostEvent)]
)
class PostListComponent implements OnInit {
final PostService _postService;
final PostEvent _postEvent;
List<Post> posts;
#Input()
Place place;
PostListComponent(this._postService, this._postEvent) {
// listen for postEvents, if received re-fetch posts
_postEvent.onEventStream.listen((int id) => _getPosts());
}
// Get all posts when page loads the first time
void ngOnInit() => _getPosts();
// Function to get all the posts related to a place
Future<void> _getPosts() async {
posts = await _postService.getPostsByPlace(place.id);
}
}
If anybody knows a better way to do it, by any means correct me, as I'm not that familiar with the framework yet, and have a hard time to understand the concepts. The documentation covers much, but if somebody is totally new to the framework, like me, I'm missing some info. An extension to the Hero Documentation would be appreciated, that covers the more complex topics, such as communication between child components, and child to parent, but not sure, if this is explained somewhere, and I just missed it.

Retrieve objects from indexes using angularfire2

I'm trying (with partial success :( ) to retrieve full objects as observables from a collection of indexes of my firebase RTDB using angularfire2 basic methods such as list() and object() in an Ionic app.
When retrieving the list of keys for the courses a user has enrolled on I make a new query and get the full data as an observable using the object() method. I get several null in the view when loading the page for the first time but the observables are still alive, so if I make a small change in those objects in the console, the whole object is retrieved and shown in the view without any problem. Am I missing something?
Firebase RTDB root-level nodes
This is my page ts code
import { Component, ViewChild } from '#angular/core';
import { NavController, NavParams, List } from 'ionic-angular';
import { ProfileServiceProvider } from '../../providers/profile-service/profile-service';
import { MomentsFeedPage } from '../moments-feed/moments-feed';
import { CourseServiceProvider } from '../../providers/course-service/course-service';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
/**
* Generated class for the MomentsPage page.
*
* See https://ionicframework.com/docs/components/#navigation for more info on
* Ionic pages and navigation.
*/
#Component({
selector: 'page-moments',
templateUrl: 'moments.html',
})
export class MomentsPage{
#ViewChild('enrolledList', { read: List }) enrolledList: List;
public enrolledLis: Observable <{}>;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public courseService: CourseServiceProvider,
public userProfile: ProfileServiceProvider,
public afDB: AngularFireDatabase
) {
if(this.userProfile.currentUser) {
console.log('constructor MomentsPage');
this.enrolledLis = this.afDB.list('/userEnrollments/'+this.userProfile.currentUser.uid).snapshotChanges()
.map( res => {
let enrolled = res;
let that = this;
return enrolled.map(key =>
that.courseService.getCourseDetail(key.key).snapshotChanges()
.map(snap =>
({ key: snap.key, ...snap.payload.val() } )
)
)
}
);
}
}
goToTopicsFeed(course: any) {
this.navCtrl.push(MomentsFeedPage, {
courseId: course.key, courseName: course.name, coursePic: course.coursePic
});
}
ionViewDidLoad() {
console.log('ionViewDidLoad MomentsPage');
}
}
And this is the code for the view
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Moments</ion-title>
</ion-navbar>
</ion-header>
<ion-content no-padding fullscreen parallax-header>
<div class="header-image" style="background-image:url('./assets/imgs/lists/wishlist-1.jpg')">
<h1>Moments</h1>
</div>
<div class="main-content">
<ion-list #enrolledList>
<ion-item-sliding *ngFor="let course of enrolledLis | async" [attr.track]="(course|async)?.degree | courseTrackPipe ">
<button ion-item (click)="goToTopicsFeed(course)" >
<ion-thumbnail item-start>
<img [src]="(course|async)?.coursePic || './assets/imgs/Film-set-greyscale.jpg'" alt="Course profile pic">
</ion-thumbnail>
<h2>{{(course|async)?.name}}</h2>
<h3>{{(course|async)?.degree}}</h3>
<p>Topics info: #topics {{(course|async)?.topicsCount}} activity...</p>
</button>
</ion-item-sliding>
</ion-list>
</div>
</ion-content>
Here you can see the behaviour:
Partial processing of the observables (courses) during first call: css class in added so some rules are applied (border in blue or red)
The first object (course observable) was updated in the firebase console and updated without issues in the view
OK. I'll answer to myself: nothing wrong with the code, maybe I tested wrong. Anyway, there was something not so good in the code: returning async Observables may lead to some problems in the (click) action. Those can be solved using a *ngIf="course" block to make sure the object is got during runtime.

routing and links angular2

I'm going to be crazy with routing, since angular2 RC1 i cannot route correctly, the last error i have is :
EXCEPTION: Error: Uncaught (in promise): Component 'LoginForm' does
not have route configuration
My code is bellow (i hoppe the usefull parts)
I cannot understand the message of error, what i want is just some links available on my main page using the '[routerLink]' routing to the correct components (my main activities)
Regards
Note : "/app/" is just a base path for my app urls (it does not correspond to a component)
bootstrap(AppComponent,[
ROUTER_PROVIDERS
]);
/////////////////////
#Component({
selector: 'my-app',
template: `
<navbartop></navbartop>
<h1>MAIN APP COMP</h1>
<a [routerLink]="['/app/coursesactivity']">CouresesActivity</a>
<router-outlet></router-outlet>
`,
directives: [ROUTER_DIRECTIVES, NavBarTop],
providers: [ROUTER_PROVIDERS]
})
#Routes([
{ path: '/app/login', component: LoginForm },
{ path: '/app', component: LoginForm }
])
export class AppComponent implements OnInit, OnActivate {
.....
}
/////////////////////
#Component({
selector: 'loginform',
templateUrl: '/app/components/compLoginForm/LoginForm.component.html',
styles: ['/app/components/compLoginForm/LoginForm.component.css']
})
export class LoginForm implements OnInit {//, OnActivate {
constructor(
//private _router: Router
//private _routeData: RouteData, deprecated
//private _routeParams: RouteParams deprecated
) {
}
...
}
I decided to cleanup the code, i encountered other errors,
finally i don't know where the original error came from...
Sorry for thos who helped me.
Regards

How to show different modal dialogs from inherited classes in Angular Dart

I have a question related to inheritance and displaying modal dialogs. I am using Angular Dart and am not sure how to accomplish what I want. I have been unsuccessful in finding any useful examples.
I currently have an abstract base Task class and a TaskA class that extends the Task class. Both are NgTwoWay components. The main html is just a simple form with a button on it. When the button is clicked, I want a modal dialog to display. This modal will either be one from TaskA or TaskB depending on which type of task was created. The problem occurs when I try to show the modal from the extended class. I don't think the TaskAComponent is getting created so the html is not accessible or does not exist yet. I have also tried making the TaskComponent abstract and TaskAComponent extending from it but then none of the task html shows up.
Here is the code I currently have.
task_component.dart
abstract class Task {
Scope _scope;
Task([this._scope]) {
}
void displayProperties();
}
#Component(
visibility: Directive.CHILDREN_VISIBILITY,
selector: 'task',
templateUrl: '../lib/components/task_component.html',
publishAs: 'taskCmp',
useShadowDom: false)
class TaskComponent {
#NgTwoWay('task')
Task task;
Scope _scope;
TaskComponent([this._scope]) {
}
void displayProperties() {
task.displayProperties();
}
}
task_A_component.dart
class TaskA extends Task {
Scope _scope;
TaskA([this._scope]) {
Modal.use();
}
void displayProperties() {
Modal myWindow;
var readComplete = querySelector('#taskA_Modal');
myWindow = new Modal(readComplete);
myWindow.show();
}
}
#Component(
visibility: Directive.CHILDREN_VISIBILITY,
selector: 'taskA',
templateUrl: '../lib/components/task_A_component.html',
publishAs: 'taskACmp',
useShadowDom: false)
class TaskAComponent {
#NgTwoWay('taskA')
TaskA taskA;
Scope _scopeA;
TaskAComponent([this._scopeA]) {
Modal.use();
}
void displayProperties() {
taskA.displayProperties();
}
}
Does anyone have any ideas what I am doing wrong? Is it possible to accomplish what I am trying to do?
Use ng-switch which conditionally create the component of your choice
EX:
<div ng-switch="ctrl.task.runtimeType">
<div ng-switch-when="Task">
<task task="ctrl.task"></task>
</div>
<div ng-switch-when="TaskA">
<taskA taskA="ctrl.task"></taskA>
</div>
</div>
My answer is very late but i hope it can help

Resources