新AngularJS ng-ref指令的陷阱

时间:2018-08-06 21:02:56

标签: angularjs angularjs-components

AngularJS V1.7.1 * 的发布引入了新的ng-ref directive。尽管此新指令使用户可以轻松地执行某些操作,但我发现滥用和问题的可能性很大。

ng-ref属性告诉AngularJS在当前作用域上发布组件的控制器。这对于使诸如音频播放器之类的组件将其API暴露给同级组件很有用。可以轻松访问其播放和停止控件。

第一个问题是播放器控件位于控制器undefined功能内的$onInit中。

Initial vm.pl = undefined  <<<< UNDEFINED
Sample = [true,false]

对于依赖于可用数据的代码,我们如何解决此问题?

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>

2 个答案:

答案 0 :(得分:3)

获取ref到组件控制器并不是什么新鲜事,早在今天的指令中就允许使用它,这根本不是问题,必须具有这样的功能,ng-ref只是您的助手从模板的角度来看(与angular 2+相同)。

尽管如此,如果您需要准备好子组件,则应使用$postLink()而不是$onInit。在组件与子组件链接后将调用$postLink,这意味着ng-ref将在调用时准备就绪。

因此,您要做的就是像这样更改onInit

̶$̶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()-在链接此控制器的元素及其子元素之后调用。与后链接功能类似,此挂钩可用于设置DOM事件处理程序并直接进行DOM操作。请注意,包含templateUrl指令的子元素将不会被编译和链接,因为它们正在等待异步加载其模板,并且它们自己的编译和链接已暂停,直到发生这种情况为止。可以将此钩子视为类似于Angular中的ngAfterViewInitngAfterContentInit钩子。由于AngularJS中的编译过程非常不同,因此没有直接映射,升级时应注意。

     

参考号:Understanding Components

下面可以找到完整的代码段(为了清楚起见,我删除了所有console.log):

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>

答案 1 :(得分:1)

父控制器初始化发生在播放器控制器初始化之前,因此这就是为什么我们在第一个initPL中将undefined作为$onInit的原因。 就个人而言,我宁愿在父控制器初始化时定义并加载应向下传递到嵌套组件的数据,而不是从其子级设置父级的初始数据。但是,即使我们需要这样做,我们也可以使用绑定和回调在子组件的初始化上完成。可能看起来更像是一个肮脏的解决方法,但是它可以在这种情况下工作,下面是代码:

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>