From here :
https://github.com/dart-lang/angular_components_example/blob/master/example/app_layout_example/lib/app_layout_example.html
I want to split this template in two templates:
one for sidebar <material-drawer>, named for example sidebar_component.{dart,html}
one other for <div class="material-content">, named for example app_component.{dart,html}
Question:
How to reach <material-drawer> from sidebar_component, with <material-button icon class="material-drawer-button" (trigger)="drawer.toggle()"> into app_component?
Components are encapsulated on purpose. So there isn't a super easy way to reach into the encapsulation of one component from the other.
What you can do is create a passthrough from one component to the other.
<side-bar #sidebar></side-bar>
<app-component (openSideBar)="sidebar.toggle()"></app-component>
sidebar_component
#Component()
class SidebarComponent {
#ViewChild(MaterialPersistentDrawerDirective)
MaterialPersistentDrawerDirective drawer;
void toggle() => drawer.toggle();
}
app_component.dart
#Component()
class AppComponent {
final _openSideBar = StreamController<void>();
#Output()
Stream<void> openSideBar => _openSideBar.stream;
// This is getting called by the trigger of the button click
void onButtonClick() => _openSideBar.add();
}
I would say that for me passing all of these events feels like a bit of a smell. The components themselves are breaking encapsulation and so I wouldn't architect the app exactly like that.
I would probably have the contents of the drawer be a component, and perhaps the header and body depending on how complex they got. To have something more like this:
<material-drawer #drawer>
<side-bar *deferredContent></side-bar>
</material-drawer>
<div class="material-content">
<app-header class="material-header shadow" (triggerDrawer)="drawer.toggle()">
</app-header>
<router-outlet></router-outlet>
</div>
I find it better to keep the app-layout logic in the same components if possible and encapsulate the pieces of that. You could also pass the drawer as an input, but then you are making those highly coupled which I tend to try not to do.
Related
I am trying to develop an UI and the first step is to create CssLayout. Each CssLayout component is added hierarchically with and many CssLayout component.
The problem is when i run the application and inspect the div tags, the class attribute has extra strings that needs to be removed.
<div class="v-csslayout v-layout v-widget .content-container v-
csslayout-.content-container v-has-width v-has-height" style="width: 100%;
height: 100%;"><div class="v-csslayout v-layout v-widget .inner-content-
container v-csslayout-.inner-content-container"></div></div>
and what I need is
<div class=".content-container">
<div class=".inner-content-container">
</div>
</div>
Java Code:
#StyleSheet("{css/spreadjsdefault.css}")
public class SpreadJSWidget extends CssLayout {
/**
*
*/
public SpreadJSWidget() {
super();
addStyleName(".content-container");
CssLayout mainBox = new CssLayout();
mainBox.addStyleName(".inner-content-container");
addComponent(mainBox);
}
spreadjsdefault.css (They are empty for now)
.content-container
{
}
.inner-content-container
{
}
Please advice !
Two things:
In order to be able to properly match the css rules, you have to omit the leading . when adding the style name, i.e. addStyleName("contentContainer"). This way, the css elements will match your style definition.
Css classes like v-csslayout are default classes defined by vaadin used by the default themes to provide a basic layout. They are there by default and can't (and actually shouldnt) be removed entirely. What you can do, however, is to define and overwrite these rules yourself. What's important: Either way, your custom classes will still match when you define them in your style sheet and can overwrite the default theming.
I have a sidenav that works great with mock data, but when I load the data for the sidenav asynchronously from a rest service, the content part renders on the entire page area, and then when the async call returns, the sidenav renders, and ends up covering part of the content area. If I close the sidenav and open again, it looks find. However, on initial load, I can't see the left part of the content.
<mat-sidenav-container fullscreen class="main-sidenav-container">
<mat-sidenav #sidenav mode="side" opened>
<mat-nav-list>
<mat-list-item disa *ngFor="let navBarItem of observableNavBarItems|async" (click)="onSelected(navBarItem.value)">
<img mat-list-icon class="{{navBarItem.icon}}" />
<h4 mat-line>{{navBarItem.name}}</h4>
</mat-list-item>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
Any thoughts on how to fix this?
i fixed a similar Problem (using the MatDrawer) by setting the following MatDrawerContainer property according to the Angular Materials documentation: https://next.material.angular.io/components/sidenav/api
#Input()
autosize: boolean
Whether to automatically resize the container whenever the size of
any of its drawers changes.
Use at your own risk! Enabling this option can cause layout thrashing
by measuring the drawers on every change detection cycle. Can be
configured globally via the MAT_DRAWER_DEFAULT_AUTOSIZE token.
in my case:
<mat-drawer-container autosize="true" >
<mat-drawer #drawer mode="side" opened="true">
...
</mat-drawer>
<mat-drawer-content>
...
</mat-drawer-content>
</mat-drawer-container>
i had the same problem and i've found a solution that works fine in my case.
First of all, i needed my component hosting the mat-nav-list being invoked as soon as its view had been rendered.
In order to do this, i choose to decorate mat-nav-list with a directive (renderingDetector).
Here is the code:
import { Directive, AfterViewInit, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[renderingDetector]'
})
export class RenderingDetectorDirective implements AfterViewInit {
#Output() onViewRendered = new EventEmitter<void>(true);
ngAfterViewInit() {
this.onViewRendered.emit();
}
}
NOTE: as a possible solution, i also tried to move the whole mat-nav-list content into a separate component in order to control its life-cycle as well as it's been done inside the directive, but this approach headed to the application recharging the page every time i clicked on an item (so that all benefits of Single Page Application went lost).
On the other side, the hosting component keeps watching on it.
<mat-nav-list renderingDetector (onViewRendered)="viewRendered()">
In order to have the mat-sidenav-content being rendered exactly beside the menus we should properly set the margin-left property.
#ViewChild('sidenavRef') sideNavRef: MatSidenav;
#ViewChild('sidenavContentRef', {read: ElementRef}) sideNavContentRef: ElementRef;
viewRendered() {
this.sideNavContentRef.nativeElement.style.marginLeft = String(this.sideNavRef._width) + "px";
}
sidenavRef and sidenavContentRef refer to the mat-sidenav and mat-sidenav-content elements in the html file
<mat-sidenav-container>
<mat-sidenav #sidenavRef>
...
</mat-sidenav>
<mat-sidenav-content #sidenavContentRef>
...
</mat-sidenav-content>
</mat-sidenav-container>
I have a set of form fields that are in a dynamically created component. The parent Component owns the form tag. However, none of the form fields are being added to the Form. I'm using the ComponentFactoryResolver to create the component:
#Component({
selector: 'fieldset-container',
templateUrl: './fieldset-container.component.html',
styleUrls: ['./fieldset-container.component.scss'],
entryComponents: ALL_FIELD_SETS,
})
export class FieldsetContainerComponent<C> {
fieldsetComponent : ComponentRef<any> = null;
#Input() formGroup : FormGroup;
#ViewChild('fieldSetContainer', {read: ViewContainerRef})
fieldsetContainer : ViewContainerRef;
#Output() onComponentCreation = new EventEmitter<ComponentRef<any>>();
constructor(private resolver : ComponentFactoryResolver) {
}
#Input() set fieldset( fieldset : {component : any, resolve : any }) {
if( !fieldset ) return; // sorry not right
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(fieldset.resolve).map((resolveName) => {return {provide: resolveName, useValue: fieldset.resolve[resolveName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.fieldsetContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(findComponentForFieldset(fieldset.component));
// We create the component using the factory and the injector
let component : ComponentRef<any> = factory.create(injector);
// We insert the component into the dom container
this.fieldsetContainer.insert(component.hostView);
// Destroy the previously created component
if (this.fieldsetComponent) {
this.fieldsetComponent.destroy();
}
this.fieldsetComponent = component;
this.onComponentCreation.emit( this.fieldsetComponent );
}
}
The template:
<div #fieldSetContainer [formGroup]="formGroup"></div>
The usage of the dynamic component:
<form class="form" #omaForm="ngForm">
<div *ngFor="let fieldset of page?.fieldsets">
<fieldset-container [fieldset]="{ component: fieldset, resolve: {} }" (onComponentCreation)="onComponentCreation($event)" [formGroup]="omaForm.form"></fieldset-container>
</div>
</form>
I suspect it has something to do with the injector not being hooked up correctly, but from what I can tell it is chained to the parent. I've set a breakpoint in NgModel and it is passed a null for parent which is the problem. I traced that back up into something that looks compiled and it was just hard coding a null. So I'm not sure how that was created with hard coded nulls in there.
Any ideas on how to fix this?
Ok it turns out it has nothing to do with the dynamic nature of this component. I removed it and defined all of my components inline and it still had the problem. The issue was that having form controls inside a Component that were nested within a form tag is just not supported by Angular out of the box. Once you nest a form control in a component it can't see the NgForm anymore which is crazy.
After reading solutions on the web and seeing that no one had a good solution I designed 2 of my own directives that registered the Form into the DI container up at the NgForm, then using DI hierarchy I could inject that into another Directive that would perform the registration below.
Parent Component Template:
<form nested>
<my-component .../>
</form>
Child Component Template:
<div>
<input name="street" [(ngModel)]="address.street" required nest/>
<input name="city" [(ngModel)]="address.city" required nest/>
<input name="state" [(ngModel)]="address.state" required nest/>
<input name="zip" [(ngModel)]="address.zip" required nest/>
</div>
Once I had this in place then I could bring back my dynamic component and it worked perfectly. It was just really hard to get there.
It's really elegant and simple and doesn't require me to pass the form instance down through the layers like so many suggestions on the web show. And the work to register a form control whether it's 1 layer or 999 layers removed is the same.
IMHO NgForm and NgModel should just do this out of the box for us, but they don't which leads to complicated architecture design to accomplish moderately advanced forms.
I have been working on a component to streamline a series of other components. The reason I wanted to do this is pretty much to create 1 place where the implementations would be since the code is the same, we don't have to worry about maintaining 17 html files, just 1.
I was trying to find a way to programmatically move 1 web component into another. some sort of A.appendHtml(B); but i was not 100% sure if that worked with WebComponents.
My goal is to move mine-b's template contents into ele-a before it renders out to the page. Essentially unwrapping the mine-b wrapper and injecting it to where it is suppose to go
<template>
<ele-a></ele-a>
<div>
<mine-b></mine-b>
</div>
</template>
<ele-a></ele-a> accepts only 2 tag types: sub-a and sub-b. sub-b can accept form tags.
<ele-a>
<sub-a></sub-a>
<sub-b>
<form></form>
</sub-b>
</ele-b>
so i thought to myself to create a component which would allow me to streamline all the attribute setting
<mine-a>
<ele-a>
<content></content>
</ele-a>
</mine-a>
<mine-b>
<sub-a></sub-a>
<sub-b>
<content></content>
</sub-b>
</mine-b>
which would allow me to do something like:
<mine-a>
<mine-b>
<form></form>
</mine-b>
</mine-a>
THe issue i have been having is that since ele-a only accepts sub-a and sub-b so i cant define it as a child of ele-a. The reason is the select attribute inside of ele-a which will prevent me from stamping out mine-b. So i tried to make it a sibling:
<mine-a>
<ele-a></ele-a>
<mine-b></mine-b>
</mine>
So this is where i am right now. In the dart for mine-a which extends PolymerElement. Is there a way to move the stamped out contents of the template of mine-b to ele-a? IE: the sub-a, and sub-b?
I was thinking to do something like:
<mine-a>
<ele-a id="targetEle"></ele-a>
<div id="sourceEle" hidden>
<content select="mine-b"></content>
</div>
</mine-a>
class MyElementA extends PolymerElement {
DivElement get _source => $['sourceEle'];
ElementA get _target => $['targetEle'];
MyElementA.created() : super.created(){
_target.appendHtml(_source.children());
}
}
or something to just move 1 WebComponent into another? I have been having some issues with this.
When looking at the PolymerLife Cycle, I was thinking I might be able to do it prior to the dom being initialized, by doing it in the created function.
I have a page made of custom components. In that page I have a button. If I click the button I have to call another page (page.mxml consisting of custom components). Then click event handler is written in Action-script, in a separate file.
How to make a object of an MXML class, in ActionScript? How to display the object (i.e. the page)?
My code:
page1.mxml
<comp:BackgroundButton x="947" y="12" width="61" height="22"
paddingLeft="2" paddingRight="2" label="logout" id="logout"
click="controllers.AdminSession.logout()"
/>
This page1.mxml has to call page2.mxml using ActionScript code in another class:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
}
Your Actionscript class needs a reference to the display list in order to add your component to the stage. MXML is simply declarative actionscript, so there is no difference between creating your instance in Actionscript or using the MXML notation.
your function:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
}
could be changed to:
static public function logout():StartSplashPage {
return new StartSplashPage();
}
or:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
myReferenceToDisplayListObject.addChild( startPage );
}
If your actionscript does not have a reference to the display list, than you cannot add the custom component to the display list. Adding an MXML based custom component is no different than adding ANY other DisplayObject to the display list:
var mySprite:Sprite = new Sprite();
addChild(mySprite)
is the same as:
var startPage:StartSplashPage = new StartSplashPage();
myReferenceToDisplayListObject.addChild( startPage );
Both the Sprite and the StartSplashPage are extensions of DisplayObject at their core.
You reference MVC in the comments to another answer. Without knowing the specific framework you've implemented, or providing us with more code in terms of the context you are trying to perform this action in, it is difficult to give a more specific answer.
I assume that you are on a page with a set of components and want to replace this set of components on the page with a different set of components. My apologies in advance if this is not what you are trying to do.
You can do this using ViewStacks and switching the selected index on selection -- this can be done either by databinding or by firing an event in controllers.AdminSession.logout() and listening for that event in the Main Page and switching the selectedIndex of the view stack in the handler function.
MainPage.mxml
<mx:ViewStack>
<views:Page1...>
...
<comp:BackgroundButton x="947" y="12" width="61" height="22"
paddingLeft="2" paddingRight="2" label="logout" id="logout"
click="controllers.AdminSession.logout()"/>
</views:Page1...>
<views:Page2 ...>
...
<comp:Comp1 .../>
<comp:Comp2 .../>
</views:Page2>
I think you may use state to do you work.
You may take a look at http://blog.flexexamples.com/2007/10/05/creating-view-states-in-a-flex-application/#more-221
Edit:
I am not sure I fully understand your case.
As I know, you may make a new state in page1.mxml, and name it, eg. secondPageState, and then put the custom component page2.mxml in the secondPageState.
In the controller, you need an import statement to import the page1 component and make a public var for the page1 component, eg. firstPage.
Then, the code will similar to:
public function logout():voild
{
firstPage.currentState = "secondPageState";
}
Another solution:
If you don't like the change state solution, you may try to use the addchild, to add the custom component to your application.