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.
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.
I'm trying to integrate asp.net mvc with an angular 2 application. I understand that this is not ideal, but I am being asked to integrate some existing Mvc functionality (think big legacy app) into a brand new Angular 2 spa.
What I would like to be able to do is have a cshtml view that has angular components in it, as well as pure mvc stuff...
<side-bar></side-bar>
<action-bar></action-bar>
#{
Html.RenderPartial("_SuperLegacyPartialView");
}
I'm struggling to find any way to do this. This blog post looked promising - http://www.centare.com/tutorial-angular2-mvc-6-asp-net-5/. It used a templateUrl value that pointed to a path rendered by Mvc, as well as AsyncRoute, but none of that works anymore in Angular 2. This post looked promising as well - http://gbataille.github.io/2016/02/16/Angular2-Webpack-AsyncRoute.html, but it uses AsyncRoute too, which is deprecated.
This used to be very easy in Angular 1. We used to either manually bootstrap angular into a Razor View, or render a partial view as the templateUrl of a component/directive. What is the best way to do this in the latest Angular 2 that uses Webpack?
I came up with a solution that satisfied my needs at the time. I'm using angular-cli with WebPack, and this worked for my needs. I don't understand all the examples I've seen that say to use "templateUrl: '/Template/Index'", where the path is a path to an MVC view. That simply doesn't work because the path can't be found inside any of the bundled views that WebPack creates. Maybe those people aren't using angular-cli and WebPack.
This stackoverflow answer - How can I use/create dynamic template to compile dynamic Component with Angular 2.0? was very helpful in creating the following directive. This directive will take the output of an mvc partial view and compile it. It allows for Razor/server logic to take place, and some angular to be compiled as well. Although, actually including other components inside this MVC partial was problematic. If you get that working, please let me know what you did. In my case, I just needed the server rendering to happen and to place that exactly where I wanted it in my Angular 2 spa.
MvcPartialDirective
import {
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector, OnInit, OnDestroy
} from '#angular/core';
import { RouterModule } from '#angular/router';
import { CommonModule } from '#angular/common';
import {Http} from "#angular/http";
import 'rxjs/add/operator/map';
export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
#NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
class DynamicHtmlModule { }
return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}
#Directive({ selector: 'mvc-partial' })
export class MvcPartialDirective implements OnInit, OnDestroy {
html: string = '<p></p>';
#Input() url: string;
cmpRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: Http) { }
ngOnInit() {
this.http.get(this.url)
.map(res => res.text())
.subscribe(
(html) => {
this.html = html;
if (!html) return;
if(this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: this.html,
});
createComponentFactory(this.compiler, compMetadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
});
},
err => console.log(err),
() => console.log('MvcPartial complete')
);
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
in some-component.html (assuming your mvc app shares the domain with your spa)
<mvc-partial [url]="'/stuffs/mvcstuff'"></mvc-partial>
MvcStuff.cshtml
#{
ViewBag.Title = "This is some MVC stuff!!!";
}
<div>
<h2>MVC Stuff:</h2>
<h4>#ViewBag.Title</h4>
<h2>Angular Stuff:</h2>
<h4>{{1 + 1}}</h4>
</div>
in StuffsController.cs
public PartialViewResult MvcStuff() => PartialView();
I did it like this.
#Component({
templateUrl: '/Template/Index'
})
export class TemplateComponent {}
"/Template/Index" is the URL in your MVC Controller, and then the method.
public IActionResult Index()
{
return PartialView();
}
My problem is i don't know how the refresh the view to call controller method every time is loaded.
For those who are on Angular 7, you will need to change the accepted answer a little bit to make it work.
In MvcPartialDirective:
Update Http to HttpClient so that it reads:
import { HttpClient } from '#angular/common/http';
In ngOnInit(), specify the responseType:
this.http
.get(this.url, {responseType: "text"})...
Update to pipe:
.pipe(map(res => res.toString())) (note toString() insteadd of .text())
Optionally is to use app prefix to directive specification:
#Directive({
selector: 'appActionResult'
})
End result:
import {
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector, OnInit, OnDestroy
} from '#angular/core';
import { RouterModule } from '#angular/router';
import { CommonModule } from '#angular/common';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent { };
const decoratedCmp = Component(metadata)(cmpClass);
#NgModule({
imports: [CommonModule, RouterModule],
declarations: [decoratedCmp],
schemas: [NO_ERRORS_SCHEMA] })
class DynamicHtmlModule { }
return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}
#Directive({
selector: 'appActionResult'
})
export class ActionResultDirective implements OnInit, OnDestroy {
html = '<p></p>';
#Input() url: string;
cmpRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: HttpClient) {}
ngOnInit() {
this.http
.get(this.url, {responseType: "text"})
.pipe(map(res => res.toString()))
.subscribe(
(html) => {
this.html = html;
if (!html) { return; }
if (this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: this.html,
});
createComponentFactory(this.compiler, compMetadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
});
},
err => console.log(err),
() => console.log('MvcPartial complete')
);
}
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
I needed to use MVC PartialView html in my angular 4 application, called by the HttpClient .get method.
I used AMD's post
to convert my partial view to an html string. I returned this in a container json object, and set it into a variable that set the html of a div on my page..thus:
...in the template
<div class="files" [innerHtml]="myTemplate">
</div>
... in the component .ts file
export interface htmldata {
html: string;
}
... inside component
getDivHtml(path: string): Promise<htmldata> {
return this.http
.get<htmldata>(`${this.baseUrl}/MVC/Index?path=` + path , { withCredentials: true })
.toPromise();
}
ngOnInit() {
this.getDivHtml('').then(
data => { this.loadData(data); },
).catch( error => { console.log(error); });
}
loadData(data: htmldata) {
this.myTemplate = data.html;
}
...on server
public class HtmlReturn
{
public string html { get; set; }
}
[Produces("application/json")]
[Route("api/MVC/[action]")]
public class MVCController : Controller
{
private readonly ViewRender view;
public MVCController(ViewRender view)
{
this.view = view;
}
public IActionResult Index(string path)
{
data.html = this.view.Render("viewpath", viewModel);
return Json(data);
}
}
Please note: this only works well with static html that doesn't need event listeners. I was not able to add click events to the loaded html with renderer2, although I am not an expert and it may be possible.
You will need to create the ViewRender class and add an injection instruction into the startup.cs file as shown in AMDs post
Using Systemjs:
https://github.com/VahidN/MVC5Angular2
Using Webpack:
http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/
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)
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