问题
The release of AngularJS V1.7.1* introduces the new ng-ref directive. While this new directive enables users to easily do certain things, I see great potential for abuse and problems.
The ng-ref attribute tells AngularJS to publish the controller of a component on the current scope. This is useful for having a component such as an audio player expose its API to sibling components. Its play and stop controls can be easily accessed.
The first problem is the player controls are undefined
inside the $onInit
function of the controller.
Initial vm.pl = undefined <<<< UNDEFINED
Sample = [true,false]
For code that depends on data being available, how do we fix this?
The DEMO
angular.module("app",[])
.controller("ctrl", class ctrl {
constructor() {
console.log("construct")
}
$onInit() {
console.log("onInit", this.pl);
this.initPL = this.pl || 'undefined';
this.sample = this.pl || 'undefined';
this.getSample = () => {
this.sample = `[${this.pl.box1},${this.pl.box2}]`;
}
}
})
.component("player", {
template: `
<fieldset>
$ctrl.box1={{$ctrl.box1}}<br>
$ctrl.box2={{$ctrl.box2}}<br>
<h3>Player</h3>
</fieldset>
`,
controller: class player {
constructor() {
console.log("player",this);
}
$onInit() {
console.log("pl.init", this)
this.box1 = true;
this.box2 = false;
}
},
})
<script src="//unpkg.com/angular@1.7.1/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as vm">
Initial vm.pl = {{vm.initPL}}<br>
Sample = {{vm.sample}}<br>
<button ng-click="vm.getSample()">Get Sample</button>
<br>
<input type="checkbox" ng-model="vm.pl.box1" />
Box1 pl.box1={{vm.pl.box1}}<br>
<input type="checkbox" ng-model="vm.pl.box2" />
Box2 pl.box2={{vm.pl.box2}}<br>
<br>
<player ng-ref="vm.pl"></player>
</body>
回答1:
Getting ref to components controller isn't new, directives back in the day allowed it and that wasn't a problem at all, it's necessary to have such feature, ng-ref
is just a helper for you to do this from the template side (the same way angular 2+ does).
Nevertheless, if you need the child components ready you should use $postLink()
instead of $onInit
. $postLink
is called after the component is linked with his children, which means, the ng-ref
will be ready when it gets called.
So all you have to do is change your onInit
like so:
̶$̶o̶n̶I̶n̶i̶t̶(̶)̶ ̶{̶
$postLink() {
console.log("onInit", this.pl);
this.initPL = this.pl || 'undefined';
this.sample = this.pl || 'undefined';
this.getSample = () => {
this.sample = `[${this.pl.box1},${this.pl.box2}]`;
}
}
$postLink()
- Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain templateUrl directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. This hook can be considered analogous to thengAfterViewInit
andngAfterContentInit
hooks in Angular. Since the compilation process is rather different in AngularJS there is no direct mapping and care should be taken when upgrading.Ref.: Understanding Components
The full working snippet can be found bellow (I removed all console.log
to make it clearer):
angular.module("app",[])
.controller("ctrl", class ctrl {
constructor() {
//console.log("construct")
}
$postLink() {
//console.log("onInit", this.pl);
this.initPL = this.pl || 'undefined';
this.sample = this.pl || 'undefined';
this.getSample = () => {
this.sample = `[${this.pl.box1},${this.pl.box2}]`;
}
}
})
.component("player", {
template: `
<fieldset>
$ctrl.box1={{$ctrl.box1}}<br>
$ctrl.box2={{$ctrl.box2}}<br>
</fieldset>
`,
controller: class player {
constructor() {
//console.log("player",this);
}
$onInit() {
//console.log("pl.init", this)
this.box1 = true;
this.box2 = false;
}
},
})
<script src="//unpkg.com/angular@1.7.1/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as vm">
Initial vm.pl = {{vm.initPL}}<br>
Sample = {{vm.sample}}<br>
<button ng-click="vm.getSample()">Get Sample</button>
<br>
<input type="checkbox" ng-model="vm.pl.box1" />
Box1 pl.box1={{vm.pl.box1}}<br>
<input type="checkbox" ng-model="vm.pl.box2" />
Box2 pl.box2={{vm.pl.box2}}<br>
<player ng-ref="vm.pl"></player>
</body>
回答2:
Parent controller initialization happens before the initialization of the player controller, so that is why we have initPL
as undefined
in the first $onInit
.
Personally, I would prefer to define and load the data that should be passed down to the nested components on the parent controller initialization instead of setting the parent's initial data from its child's. But still if we will need this we can do it on the child's component initialization using bindings and callbacks. Probably it looks more like a dirty workaround, but it could work in such scenarios, here is the code:
angular.module("app",[])
.controller("ctrl", class ctrl {
constructor() {
console.log("construct")
}
$onInit() {
console.log("onInit", this.pl);
this.getSample = () => {
this.sample = `[${this.pl.box1},${this.pl.box2}]`;
}
this.onPlayerInit = (pl) => {
console.log("onPlayerInit", pl);
this.initPL = pl || 'undefined';
this.sample = `[${pl.box1},${pl.box2}]`;
}
}
})
.component("player", {
bindings: {
onInit: '&'
},
template: `
<fieldset>
$ctrl.box1={{$ctrl.box1}}<br>
$ctrl.box2={{$ctrl.box2}}<br>
<h3>Player</h3>
</fieldset>
`,
controller: class player {
constructor() {
console.log("player",this);
}
$onInit() {
console.log("pl.init", this)
this.box1 = true;
this.box2 = false;
if (angular.isFunction( this.onInit() )) {
this.onInit()(this);
}
}
},
})
<script src="//unpkg.com/angular@1.7.1/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as vm">
Initial vm.pl = {{vm.initPL}}<br>
Sample = {{vm.sample}}<br>
<button ng-click="vm.getSample()">Get Sample</button>
<br>
<input type="checkbox" ng-model="vm.pl.box1" />
Box1 pl.box1={{vm.pl.box1}}<br>
<input type="checkbox" ng-model="vm.pl.box2" />
Box2 pl.box2={{vm.pl.box2}}<br>
<br>
<player ng-ref="vm.pl" on-init="vm.onPlayerInit"></player>
</body>
来源:https://stackoverflow.com/questions/51715591/pitfalls-of-the-new-angularjs-ng-ref-directive