I've got three Polymer-components (Polymer 1.2).
They all sit in their own files therefore is-logged-in and login-name have to be passed from one component to another.
I put them together here so you can understand my problem more easily:
<component1 is-logged-in="true" login-name="Cool Cat">
<component2 is-logged-in="{{isLoggedIn}}" login-name="{{loginName}}">
<component3 is-logged-in="{{isLoggedIn}}" login-name="{{loginName}}"></component3>
</component2>
</component1>
All 3 components have these properties:
properties: {
isLoggedIn: {
type: Boolean,
value: false
},
loginName: {
type: String,
value: ""
}
}
document.querySelector("component2").loginName is Cool Cat but
document.querySelector("component3").loginName is just an empty string.
When checking the DOM is-logged-in and login-name don't appear anymore starting at <component2>
How can I pass the data on to component3 ?
All {{ }} bindings have to live in template, and it's the template which identifies the scope of the values. Other parent-child relationships do not define scope.
In your example, all the component-1/2/3 are in the same template, and therefore in the same scope. Setting properties of component-1 has no effect on component-2 and component-3, they are not bound together.
In other words, the {{isLoggedIn}} and {{loginName}} macros are binding to properties in the scope identified by the containing template (the scope is usually an element, but can also be a dom-repeat or other specialized template).
I don't expect this is actually want you want, but for clarity, something like this would work:
<dom-module id="component-0">
<template>
<component-1 is-logged-in="{{isLoggedIn}}" login-name="{{loginName}}">
<component-2 is-logged-in="{{isLoggedIn}}" login-name="{{loginName}}">
<component-3 is-logged-in="{{isLoggedIn}}" login-name="{{loginName}}"></component-3>
</component-2>
</component-1>
...
<script>
Polymer({
is: 'component-0',
properties {
isLoggedIn: {value: true},
loginName: {value: "cool-cat"}
}
</script>
All the {{ }} bindings are in the component-0 scope, so setting the values in that scope sets the values to all the bindings.
Fwiw, you will probably also have an easier time if you aggregate the shared data into an object.
<component-1 login="{{login}}">
<component-2 login="{{login}}">
<component-3 login="{{login}}"></component-3>
Where e.g. login = {isLoggedIn: true, loginName: "cool-cat"}.
The idea that the data must be passed from one component to another is not true in this construction. If your goal is just to get the data to component-3, you can bind the data directly and ignore 1 and 2.
The only time data must be passed from one component to another is when crossing scopes (a scope defines a boundary for data, so hopefully this make sense).
Related
So, I have a Vuex state that looks like so:
state: {
keylist: ['key1', 'key2', 'key3'],
items: {
key1: {title: "First Item"},
key2: {title: "Second Item"},
key3: {title: "Third Item"}
}
}
And I have a list component, referenced from root like so:
<event-list :list="this.$store.state.keylist"></event-list>
The components is defined like so:
Vue.component('event-list', {
template: "<ul><li v-for='key in list'>{{ key }}</li></ul>",
props: {
list: {
type: Array,
required: true
}
}
})
Now, this all displays the key just fine.
But, of course, what I really want to do is use a component on each item, found by it's key. And that's where I am stuck. I have an item component like so:
Vue.component('event-list-item', {
template: "<h4>{{ item.title }}</h4>",
props: {
item: {
type: Object,
required: true
}
}
})
But I cannot figure out how to translate the key in the parent component into an item in the child component. This template barfs on the first curly brace:
<ul><li v-for='key in list'><event-list-item :item="this.$store.state.items.{{key}}"</li></ul>
And in any case, that doesn't look like the right solution! so What is the right one?
To me the first thing that comes to mind is that's your nested item has no place to be inserted anyway. You might want to have a look at slots for nesting components like that:
https://v2.vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
Anyway, a few considerations:
In your use case you are better off having the list in the event list item and repeat just the element that you actually need, rather than retrieving it in a useless component that only wraps around another component. I mean:
Vue.component('event-list-item', { template: "<li v-for="item in list"><h4>{{ item.title }}</h4></li>
The store is considered the single source of truth also because it should be easier to access it from the directly impacted components, sparing you from having to hand down multiple props for several layers. Like you are doing. This base is kinda brittle for deeply nested components.
Keep in mind that each component has a different scope and they don't share a thing that's not explicitly passed.
You should access the state items fields like key :
<ul><li v-for='key in list'><event-list-item :item='key' /></li></ul>
and in tht child component :
template: "<h4>{{ store.state.items[item].title }}</h4>"
There is no problem of iterating through properties of items object. It will work the same way as iterating through an array.
<ul>
<li
v-for="item in items"
:key="item.title"
>
<event-list-item :item="item" />
</li>
</ul>
For better practice, you can format data inside a getter, to assign keys and return a list that is ready for rendering so no additional logic is delegated to the component.
Note that key used in code example is for internal use of Vue, as it is a reserved special attribute and is suggested to be present when using v-for loops.
I have two directives, each displaying a list of documents, but in a slightly different way : One displays user-favorited documents, and the other one displays user-pinned documents. These two properties depend on two object members specified for each document, i.e. :
document = {
pinned: true,
favorite: false
};
Each directive displays a frame with its own title, according to the type of documents we want to display. For refactoring purposes, I use the same template for both, and specify varying template strings and objects in two controllers, each one dedicated to a directive. (i.e. the service member to call to get the documents we want is specified in a string, since the handling of these is exactly the same)
…Until I realized the two controllers were almost identical, the only thing that changed being… template strings.
So what I came up with is using the exact same controller and template (DocumentsPanel), but still with two directives, the only difference in them being link() :
function documentsPanelFavorites(templateService, resources) {
return {
restrict: 'A',
templateUrl: templateService.getUrl('Documents/Panel/documentsPanel.html'),
controller: 'DocumentsPanel',
scope: true,
link: link
};
function link(scope) {
//Used to show a favorite/pinned checkmark for each document entry
scope.documentOptions = {
showFavoriteCheckmark: true,
showPinnedCherkmark: false
};
scope.panelName = resources.text.MODULE_TITLE_FAVORITE_DOCUMENTS;
scope.className = 'favorites';
scope.noDocumentText = 'No favorite for this user';
// Used by the controller to know which method of the
// document dataService to call to get the listed documents
scope.documentList = 'favoriteDocuments'
// etc.
}
};
The documentsPanel.html template then uses these strings defined in the controller's scope via link().
Note: Another directive used to represent a document in a list is included in documentsPanel.html, that's why I set both showPinned and showFavorite options in each directive : It's the same directive that displays each document, and it is used elsewhere with all settings to true.
Would that be considered good practice? If not, what would be a better solution?
Thanks in advance!
I would expect documents="document | filter:{pinned:true}" and documents="document | filter:{favorite:true}"... Considering title, no document text, etc. I would first create config object and pass it to directive: config.title = '...', config.nodoctext = ... But if number of this strings params is too big, just create 2 templates.
I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.
I have a child controller that references values from it's parent.
The problem is: ng-repeat within the child controller view doesn't get updated when the parent controller values are updated.
So my question is: How does one update the child controller ng-repeat when parent controller values are updated while child values are by reference?
Child Controller:
angular.module('angularApp').controller('PostController', function ($scope)
{
$scope.mainController = $scope.$parent.getMainController();
$scope.editController = $scope.$parent;
$scope.posts = $scope.mainController.currentStation.posts;
$scope.featuredAlbums = $scope.$parent.featuredAlbums;
$scope.updatePost = function(postId){
$scope.editController.updatePost(postId);
};
$scope.updateFeatured = function(featuredId){
$scope.editController.updateFeatured(featuredId);
};
});
ng-repeat under the child controller
<div ng-controller="PostController" class="posts">
<div ng-repeat="featuredAlbum in featuredAlbums">
Example that breaks:
http://plnkr.co/edit/GKjYAWEEWOrp84bwIIOt?p=info
** Answer **
Thanks for the fast response guys, I realise that everything created within the controller is passed by value and not reference, even values referenced from parent controllers are recreated as locally scoped controller values.
So the solution? Simple.
Just call the parent directly instead of recreating locally scoped vars
$scope.$parent.$someValue
Imagine the scenario:
app.controller('ParentController', function($scope) {
$scope.rawValue = 3;
$scope.hiddenValue = false;
$scope.objectValue = {
name: 'David',
age: 27
};
$scope.someFunction = function(input) {
return input;
}
});
app.controller('ChildController', function($scope) {
$scope.hiddenValue = true;
//Notice i don't need to wrap calls to parent functions or reassign parent data.
//this is because the $scope object will automatically inherit from it's parent.
});
<div ng-controller="ParentController">
{{ hiddenValue }} //false, remains false as setting only occured on child scope;
{{ rawValue }} //3, remains 3 as setting will only occur on child scope;
{{ objectValue.name }} // 'David' however will dynamically update with input below.
<div ng-controller="ChildController">
{{ hiddenValue }} //true because now it's scoped;
<input type="button" ng-click="someFunction('hello')" value="calls function on parent scope" />
<input type="text" ng-model="rawValue" /> //will initialise as 3 but as soon as updated it will be scoped on this scope.
<input type="text" ng-model="objectValue.name" /> //will initialise as David and will correctly update parent scope as edited.
</div>
</div>
So why does this work?
Anytime you are accessing a property or function it will automatically travel up the $scope hierarchy to find the value. No need to specify $parent expressly as this is how javascript inheritance works.
However whenever you are modifying/setting a value it will occur on the nearest $scope and be 'scoped'. that's what happens with hiddenValue and rawValue in example above. however notice that it works as expected on objectValue.name this is because in order to set the name property you must first 'get' objectValue. therefore javascript inheritance travels up the scope chain to get objectValue from the parent scope and then sets it's name property.
Two guidelines:
ng-model should usually use a '.' so that it forces this scope walking.
using $parent is usually a bad sign. If used correctly parent properties should already be available through the current $scope alone.
I am not sure I understand your question correctly. So I created a plunker that included two controllers. It seems to me that child values are always updated. Can you show us how your original questions are related or not?
`http://plnkr.co/edit/9aHqdbbIe5aGJSuHogPA?p=preview`
What's happening is that you are getting a new copy of the data in the child scope and the parent scope is not updated.
The simplest way to make it work is not to store any objects that need to be accessible by child controllers directly on the parent scope. You will have less code and far fewer complications.
In your case, in the parent have something like:
$scope.media = {
featuredAlbums : [ ... ],
currentStation : {
posts : {...}
}
}
$scope.functions = {
updatePost : function (pid) {
// your function
},
updateFeatured : function (pid) {
// your function
}
}
and in the child don't bother with all the inheritance and just call functions directly :
$scope.functions.updatedFeatured(featureID);
$scope.functions.updatePost(postId);
it doesn't matter which parent controller your functions and data are in, it will find them if you attach to a property of the controller but not the controller itself.
Take a look at this video which explains it better.
https://egghead.io/lessons/angularjs-the-dot
EDIT
As David Beech points, there is no need to store parent scope functions in a property because the function is not being modified. However, this approach is meant to be a simple rule that will work without any extra thinking about which data/functions are read-only and which are writable.
I am reading the docs of Google Polymer,there are two types of data binding,Node.bind() and Template Binding,so, what is the difference between Node.bind() and Template Binding
They're both ways of achieving data-binding. One in the context of DOM nodes and the other in the context of templates.
Node.bind()
Node.bind() allows you to tell DOM nodes to bind a named property to some data that you provide. So below, we're binding the property value (object.path.to.value) to the .textContent in our textNode.
var obj = {
path: {
to: {
value: 'hi'
}
}
};
var textNode = document.createTextNode('mytext');
textNode.bind('textContent', obj, 'path.to.value');
This is neat because anytime value changes, Node.bind() keeps your .textContent updated.
TemplateBinding
The Polymer docs state that this extends what you can do with the HTML <template> tag - specifically, giving you access to four new attributes: bind, repeat, if and ref.
So let's say that we wanted to pass a propery foo to a <template> which we would like to use in our template content, but want to keep in sync so that anytime foo changes, the template also gets updated. With bind that's as straight-forward as:
<template bind="{{ foo }}">
Creates a single instance with {{ foo }} when singleton model data is provided.
</template>
repeat is really useful for when you're working with lists or collections of data - like users:
<template repeat="{{ user in users }}">
<li>{{user.name}}</li>
</template>
if is really handy for conditional values in templates:
<template if="{{ conditionalValue }}">
Binds if and only if conditionalValue is truthy. (same as *bind if*)
</template>
and so on. You can find more examples of what can be done with these attributes in the TemplateBinding docs.