Angular dart child component scope - scope

How to limit component events to it's parent/child components and prevent bubbling up.
I have the following two components:
ParentComponent.dart
#Component(
selector: "parent-component",
template: '<div>parent value: {{value}}<content></content></div>',
useShadowDom: false
)
class ParentComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void set scope(Scope scope){
_scope = scope;
_scope.on('select-child').listen((ScopeEvent event){
value = event.data['value'];
});
}
}
ChildComponent.dart
#Component(
selector: "child-component",
template: '<div ng-click="select()">child: {{value}}</div>',
useShadowDom: false
)
class ChildComponent implements ScopeAware{
Scope _scope;
#NgAttr('value')
String value;
void select(){
_scope.parentScope.broadcast('select-child', {
'value': value
});
}
void set scope(Scope scope){
_scope = scope;
}
}
When one clicks on the child component, the parent updates its value.
But when i have more parent components they listen all to the same childs:
<!-- Parent 1 -->
<parent-component>
<child-component value="foo"></child-component>
<child-component value="bar"></child-component>
</parent-component>
<!-- Parent 2 -->
<parent-component>
<child-component value="herp"></child-component>
<child-component value="derp"></child-component>
</parent-component>
When i click the foo child-component of parent1 both parent components change their value to 'foo'.
I already tried playing with emit, broadcast. I know broadcast bubbles downwards to the leaf nodes and emit bubbles up. I also tried usind "scope.createChild()" but i think i miss something.
How can i create a scope in parent which is only visible to the child and vice versa?
Or how to use broadcast emit correctly?
When i understand the docs right i have to use emit() in child-component and not parentNode.broadcast() but i can't get it to work

Good question. Your confusion is coming from the fact that while components do create new scopes, those scopes are only available to the shadowDom (the html in the template: annotation).
In your example all 6 components - 2 parents and 4 children, create scopes that are child scopes of root scope. So when you are calling parentScope.broadcast you are firing event from rootScope down to all 6 scopes.
The easiest way to achieve the behavior you want is to directly inject ParentComponent into ChildComponent. Parent components are injectable to components in both their light and shadow DOMs.
class ChildComponent implements ScopeAware {
ParentComponent _p;
ChildComponent(this._p);
void select(){
_p.setValue(value);
}
...
}
Only downside is that makes the two components more tightly coupled to each other, which is ok if you are authoring both for your own application.

Related

LitElement <slot> not wokring

I'm creating my custom accordion element. In which I'll have 2 components 1 for ul and other for li.
Content in file accordion-ul.ts, in which I've a slot where I want my li.
import { html, customElement, property, LitElement } from 'lit-element';
import { Accordion } from 'carbon-components';
#customElement('accordion-panel')
export default class AccordionPanel extends LitElement {
firstUpdated() {
const accordionElement = document.getElementById('accordion');
Accordion.create(accordionElement);
}
connectedCallback() {
super.connectedCallback();
}
render() {
return html`
<ul data-accordion class="accordion" id="accordion">
<slot></slot>
</ul>
`;
}
createRenderRoot() {
return this;
}
}
NOTE: I'm getting an error in the console in the firstUpdated() : Uncaught (in promise) TypeError: Cannot read property 'nodeName' of null.
The way I'm using it for testing:
<accordion-panel><li>test</li></accordion-panel>
IDK, it's not working and nothing is printing on the screen. On inspecting the element, I can see there's empty in DOM.
Your problem is that you're trying to use slots, which are a shadow DOM feature but you're not using shadow DOM (since you're overwriting the createRenderRoot method to prevent the creation of the shadowRoot)
So, if you want to use slots, just remove the createRenderRoot function from your class and use shadow DOM
Edit:
You should also update your firstUpdated method so that this part:
const accordionElement = document.getElementById('accordion');
Uses the element from your shadow DOM
const accordionElement = this.shadowRoot.querySelector('.accordion');
Then again, CarbonComponents styling will probably not work so you'll need to add those in some other way

Angular 2: Passing form data from child to parent only if it is valid

I have an angular reactive form in the child component and Submit button in the parent component. By default, Submit button in the parent component is disabled. I need a mechanism through which I should be able to check the form status in the child component, whether it is valid or invalid.
The moment status valid becomes true, the button in parent component should be enabled.
How to accomplish this?
my solution
app.child.ts
#Component({
selector: 'app-child',
template: `...`
})
export class AppChild {
form: FormGroup;
}
app.parent.html
<app-child #child></app-child>
<button [disabled]="child.form.invalid">Submit</button>
you need subscribe for valueChanges event in your child component, in which you can emit the value of your form,
this.formname.valueChange.subscribe(status=>{
this.valueEmit.emit(this.formname.valid)
});
In you parent comonents template check status of that emitted event.

VueJs Calling method in Child components

I have a prop
<graph :active-metrics="$data.active_metrics"></graph>
In my child component I can access the value
export default {
template: '<div>{{activeMetrics}}</div>',
props: ['active-metrics'],
methods: {
What I need to do is trigger a method in the child whenever there is a change. How can I achieve this?
You can use v-bind to make the data from the parent flow down to the child.
In your case it would look something like this:
<graph v-bind:active-metrics="$data.active_metrics"></graph>
export default {
template: '<div>{{activeMetrics}}</div>',
props: ['active-metrics'],
watch: {
'active-metrics': function(){
alert('active-metrics updated');
}
}
See here for a working JSFiddle.

How to create an SVG component dynamically in Angular2?

I am creating a web application which uses SVG.
I have created components consist of SVG element, and they are put into a root svg element.
They have attribute selector, because SVG/XML document tree is strict so I cannot use element selector.
And they have a template starts with svg:g tag:
#Component({
selector:'[foo]',
template: '<svg:g>...</svg:g>',
})
In the application, I want to create a component when a user press a button,
and simultaneously start dragging it.
I thought it can be achieved by creating a component dynamically using ComponentResolver:
#ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef
private componentResolver: ComponentResolver
onMouseDown() {
this.componentResolver
.resolveComponent(FooComponent)
.then((factory) => {
const dynamicComponent = this.dynamicComponentTarget.createComponent(factory, 0)
const component: FooComponent = dynamicComponent.instance
const element = dynamicComponent.location.nativeElement
// add event listener to start dragging `element`.
})
}
Component is created when onMouseDown() called, but its DOM element is div, so it is illegal element in svg document and cannot be displayed.
I have tried with selector='svg:g[foo]', then g element is created, but its namespace is not for SVG (http://www.w3.org/2000/svg), but normal HTML namespace (http://www.w3.org/1999/xhtml) and its class is HTMLUnknownElement > g.
I also tried with selector='svg:svg[foo]', then svg:svg element is created and it is displayed. But svg:svg cannot move with transform attribute so this doesn't work well for my application.
How can I dynamically create svg:g element for attribute selector component?
I am using Angular2: 2.0.0-rc4.
You're right about the namespacing issues keeping the g element from rendering as svg. Unfortunately, attaching the node as an svg element is the only way to feasibly get the component to namespace properly.
However, this doesn't mean this won't work. If you add the drag functionality as a directive on the g element in the template, it will be compiled with your component, and you can offset your logic into that directive. The top level svg will be namespaced correctly, and the template will inherit this accordingly.
import {Component, Input} from '#angular/core';
#Component({
selector: 'svg:svg[customName]', // prevent this from hijacking other svg
template: '<svg:g dragDirective>...</svg:g>', // note the directive added here
style: []
})
export class GComponent {
constructor() { }
}
This may not be ideal, but until https://github.com/angular/angular/issues/10404 is resolved, there's not much of an alternative.
I am not sure that my solution is right but it works for me (even with ivy):
#Component({
...
})
class ParentComponent {
constructor(
private injector: Injector,
private appRef: ApplicationRef,
private componentFactoryResolver: ComponentFactoryResolver,
) {}
createDynamicComponent() {
let node = document.createElementNS("http://www.w3.org/2000/svg", "svg");
let factory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
let componentRef = factory.create(this.injector, [], node);
this.appRef.attachView(componentRef.hostView);
}
}
After that you must manually append node into DOM.
Instead of trying to create your component to the view with the Component Resolver I will do this instead.
Create a object with properties which match the attributes you want to pass to your SVG Component.
Append this object to an array (ex.svgItems).
Add *ngFor="svgItem in svgItems" to the SVG component you want to create dynamically.
Hope it's clear and solve your problem.

angularjs transclusion scope access

I've set up a general dialog directive with a title and apply/cancel buttons.
The dialog has an isolated scope.
The content of the dialog directive is transcluded and therefor it's scope is a sibling of the dialog scope:
From the Angular js docs:
However isolated scope creates a new problem: if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything. For this reason the transcluded scope is a child of the original scope, before the widget created an isolated scope for its local variables. This makes the transcluded and widget isolated scope siblings.
This presents a new problem for me though. The transcluded DOM should be able to respond the dialog when it is applied. Therefor I'd like to setup an 'applied' property on the dialog and let the transcluded DOM watch it. This is not possible though, because they are siblings!
Where am I going wrong?
I've run into something similar and there are 2 ways (that I know of) to access the transcluded scope directly.
The first is to create the scope yourself inside a compile function and then pass it to the transclude linking function along with a clone linking function:
function compileFn(tElement, tAttrs, transclude) {
return linkFn;
function linkFn(scope, element, attrs) {
scope = scope.$new();
scope.name = attrs.works1;
transclude(scope, function(clone) {
element.find('div').append(clone);
});
};
}
The second is to create a controller and inject the $transclude service which is pre-bound to a new scope. Your clone linking function will receive the new scope as its 2nd parameter:
function Controller($element, $attrs, $transclude) {
$transclude(function(clone, scope) {
scope.name = $attrs.works2;
$element.find('div').append(clone);
});
}
In both cases you'll have to provide a clone linking function to do the transclusion yourself instead of using ngTransclude.
See http://jsfiddle.net/dbinit/wQC7G/6/ for examples of both.
Oke, I think I've found a solution.
I've wrapped the actual dialog in a directive that defines the scope over the dialog.
The content of the dialog is still transcluded in the dialog, but since it will take it's parent scope from the parent of the dialog (!!) and not the dialog itself (transclusion works this way), this will work quite nicely.
Further, I can have the sg-import directive respond when the dialog is applied by using a &property on the dialog. When the dialog is applied, I have it evaluate the sg-apply function in context of the parent scope (the scoping is done automatically, I just have to call the method from the controller's apply() function).
<div sg-import>
<div
sg-dialog title="Import Photographs"
visible="show_import_dialog"
sg-apply="upload()"
>
<div class="drop-zone">
<div sg-photo title="{{ file.name }}">
</div>
<input type="file" multiple />
</div>
</div>
</div>
If you're willing to create a model in the common ancestor to act as a switchboard with $watch targets, you can use pre-existing facilities to having each directive mutate and/or watch that switchboard model. The component's mode of access and the content content's controller have two fery idfferent calls signatures for each scope, and there is a slight "gotcha" for the transcluded case.
Isolated Scope with Bi-Directional Binding
When registering the directive's isolate scope, =attrName" will cause examination of the domainName property named "attrName". Angular will set up bi-directional binding such that a change to the value in either scope's model affect the model in the sibling scope as well.
Example
In controller-parent.js:
module.controller( 'ParentController', function() {
$scope.switchboard = { };
}
In directive-sg-dialogue.js
return {
scope: {
isolatedPeer: "=dialogModel"
};
... in the directive metadata ...
<div ng-controller="ParentController">
<sg-dialog dialog-model="switchboard">
<div ng-controller="ChildController"></div>
</sg-dialog>
</div>
... in some applicaton view template, and ...
$scope.switchboard = { isApplied: false }
... in controller(s) bound to the application view template...
Then you are all set to ...
$scope.$watch( 'switchboard.isApplied', function(newValue, oldValue) { })
... in the common ancestor, and get a callback after ...
isolatedPeer.isApplied = true;
... in the isolated scope.
Prototypical Inheritance
As long as you don't explicitly set $scope.swtichboard in the transcluded child, you can access "switchboard.isApplied" from angular expressions in the transcluded child and have the interpolation engine "find" the value thaat was allocate dand stored by the parent controller in its own scope.
For example, the following callback will get invoked wheneve rthe pair dialogue box is closed:
$scope.$watch( 'switchboard.isApplied', function(newValue, oldValue) { } );
This works because the transcluded child is always given an basic scope, not an isolated scope.
Hope this is helpful!

Resources